IGNITE-9845 Web Console: Added support for two way SSL between browser, web server, agent and cluster.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/9f9bb752 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/9f9bb752 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/9f9bb752 Branch: refs/heads/ignite-601 Commit: 9f9bb75201a3c4a4ca5398c450d741a095321167 Parents: ca93282 Author: Alexey Kuznetsov <akuznet...@apache.org> Authored: Thu Dec 27 16:36:02 2018 +0700 Committer: Alexey Kuznetsov <akuznet...@apache.org> Committed: Thu Dec 27 16:36:02 2018 +0700 ---------------------------------------------------------------------- .../ignite/ssl/DelegatingSSLContextSpi.java | 4 +- .../ignite/ssl/SSLSocketFactoryWrapper.java | 64 ++-- modules/web-console/assembly/README.txt | 46 ++- .../web-console/backend/app/browsersHandler.js | 34 +- modules/web-console/backend/app/settings.js | 66 +++- .../backend/config/settings.json.sample | 51 +-- modules/web-console/backend/launch-tools.js | 6 +- modules/web-console/backend/package.json | 2 +- modules/web-console/frontend/app/app.js | 3 - .../connected-clusters-badge/controller.js | 11 +- .../app/modules/agent/AgentManager.service.js | 12 +- .../frontend/app/modules/demo/Demo.module.js | 24 +- .../frontend/app/utils/SimpleWorkerPool.js | 2 +- modules/web-console/frontend/app/vendor.js | 1 - modules/web-console/frontend/package.json | 5 +- .../web-console/frontend/webpack/webpack.dev.js | 25 +- modules/web-console/web-agent/pom.xml | 18 +- .../console/agent/AgentConfiguration.java | 330 +++++++++++++++++-- .../ignite/console/agent/AgentLauncher.java | 89 +++-- .../apache/ignite/console/agent/AgentUtils.java | 140 +++++++- .../agent/handlers/AbstractListener.java | 9 +- .../console/agent/handlers/ClusterListener.java | 77 ++--- .../console/agent/handlers/RestListener.java | 6 +- .../ignite/console/agent/rest/RestExecutor.java | 84 +++-- .../agent/rest/RestExecutorSelfTest.java | 329 ++++++++++++++++++ .../testsuites/IgniteWebAgentTestSuite.java | 33 ++ .../web-agent/src/test/resources/ca.jks | Bin 0 -> 1394 bytes .../web-agent/src/test/resources/client.jks | Bin 0 -> 2030 bytes .../web-agent/src/test/resources/generate.bat | 122 +++++++ .../web-agent/src/test/resources/generate.sh | 111 +++++++ .../src/test/resources/jetty-with-ciphers-0.xml | 94 ++++++ .../src/test/resources/jetty-with-ciphers-1.xml | 94 ++++++ .../src/test/resources/jetty-with-ciphers-2.xml | 94 ++++++ .../src/test/resources/jetty-with-ssl.xml | 89 +++++ .../web-agent/src/test/resources/server.jks | Bin 0 -> 1419 bytes 35 files changed, 1794 insertions(+), 281 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java b/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java index d8621f2..360e922 100644 --- a/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java +++ b/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java @@ -31,7 +31,6 @@ import javax.net.ssl.TrustManager; /** */ class DelegatingSSLContextSpi extends SSLContextSpi { - /** */ private final SSLContext delegate; @@ -57,8 +56,7 @@ class DelegatingSSLContextSpi extends SSLContextSpi { /** {@inheritDoc} */ @Override protected SSLServerSocketFactory engineGetServerSocketFactory() { - return new SSLServerSocketFactoryWrapper(delegate.getServerSocketFactory(), - parameters); + return new SSLServerSocketFactoryWrapper(delegate.getServerSocketFactory(), parameters); } /** {@inheritDoc} */ http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java b/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java index bfe6d0d..992f836 100644 --- a/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java +++ b/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java @@ -24,9 +24,10 @@ import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; -/** */ +/** + * SSL socket factory that configure additional SSL params for socket. + */ class SSLSocketFactoryWrapper extends SSLSocketFactory { - /** */ private final SSLSocketFactory delegate; @@ -49,65 +50,46 @@ class SSLSocketFactoryWrapper extends SSLSocketFactory { return delegate.getSupportedCipherSuites(); } - /** {@inheritDoc} */ - @Override public Socket createSocket() throws IOException { - SSLSocket sock = (SSLSocket)delegate.createSocket(); - + /** + * Configure socket with SSL parameters. + * + * @param socket Socket to configure. + * @return Configured socket. + */ + private Socket configureSocket(Socket socket) { if (parameters != null) - sock.setSSLParameters(parameters); + ((SSLSocket)socket).setSSLParameters(parameters); - return sock; + return socket; } /** {@inheritDoc} */ - @Override public Socket createSocket(Socket sock, String host, int port, boolean autoClose) throws IOException { - SSLSocket sslSock = (SSLSocket)delegate.createSocket(sock, host, port, autoClose); - - if (parameters != null) - sslSock.setSSLParameters(parameters); + @Override public Socket createSocket() throws IOException { + return configureSocket(delegate.createSocket()); + } - return sock; + /** {@inheritDoc} */ + @Override public Socket createSocket(Socket sock, String host, int port, boolean autoClose) throws IOException { + return configureSocket(delegate.createSocket(sock, host, port, autoClose)); } /** {@inheritDoc} */ @Override public Socket createSocket(String host, int port) throws IOException { - SSLSocket sock = (SSLSocket)delegate.createSocket(host, port); - - if (parameters != null) - sock.setSSLParameters(parameters); - - return sock; + return configureSocket(delegate.createSocket(host, port)); } /** {@inheritDoc} */ @Override public Socket createSocket(String host, int port, InetAddress locAddr, int locPort) throws IOException { - SSLSocket sock = (SSLSocket)delegate.createSocket(host, port, locAddr, locPort); - - if (parameters != null) - sock.setSSLParameters(parameters); - - return sock; + return configureSocket(delegate.createSocket(host, port, locAddr, locPort)); } /** {@inheritDoc} */ @Override public Socket createSocket(InetAddress addr, int port) throws IOException { - SSLSocket sock = (SSLSocket)delegate.createSocket(addr, port); - - if (parameters != null) - sock.setSSLParameters(parameters); - - return sock; + return configureSocket(delegate.createSocket(addr, port)); } /** {@inheritDoc} */ - @Override public Socket createSocket(InetAddress addr, int port, InetAddress locAddr, - int locPort) throws IOException { - SSLSocket sock = (SSLSocket)delegate.createSocket(addr, port, locAddr, locPort); - - if (parameters != null) - sock.setSSLParameters(parameters); - - return sock; + @Override public Socket createSocket(InetAddress addr, int port, InetAddress locAddr, int locPort) throws IOException { + return configureSocket(delegate.createSocket(addr, port, locAddr, locPort)); } - } http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/assembly/README.txt ---------------------------------------------------------------------- diff --git a/modules/web-console/assembly/README.txt b/modules/web-console/assembly/README.txt index f1e114c..2656aca 100644 --- a/modules/web-console/assembly/README.txt +++ b/modules/web-console/assembly/README.txt @@ -43,12 +43,13 @@ Technical details All available parameters with defaults: Web Console host: --server:host 0.0.0.0 Web Console port: --server:port 80 + Enable HTTPS: --server:ssl false - HTTPS key: --server:key "serve/keys/test.key" - HTTPS certificate: --server:cert "serve/keys/test.crt" - HTTPS passphrase: --server:keyPassphrase "password" + Disable self registration: --server:disable:signup false + MongoDB URL: --mongodb:url mongodb://localhost/console + Mail service: --mail:service "gmail" Signature text: --mail:sign "Kind regards, Apache Ignite Team" Greeting text: --mail:greeting "Apache Ignite Web Console" @@ -56,8 +57,31 @@ All available parameters with defaults: User to send e-mail: --mail:auth:user "someusername@somecompany.somedomain" E-mail service password: --mail:auth:pass "" -Sample usage: - `ignite-web-console-win.exe --mail:auth:user "my_u...@gmail.com" --mail:auth:pass "my_password"` +SSL options has no default values: + --server:key "path to file with server.key" + --server:cert "path to file with server.crt" + --server:ca "path to file with ca.crt" + --server:passphrase "Password for key" + --server:ciphers "Comma separated ciphers list" + --server:secureProtocol "The TLS protocol version to use" + --server:clientCertEngine "Name of an OpenSSL engine which can provide the client certificate" + --server:pfx "Path to PFX or PKCS12 encoded private key and certificate chain" + --server:crl "Path to file with CRLs (Certificate Revocation Lists)" + --server:dhparam "Diffie Hellman parameters" + --server:ecdhCurve "A string describing a named curve" + --server:maxVersion "Optional the maximmu TLS version to allow" + --server:minVersion "Optional the minimum TLS version to allow" + --server:secureOptions "Optional OpenSSL options" + --server:sessionIdContext "Opaque identifier used by servers to ensure session state is not shared between applications" + --server:honorCipherOrder "true or false" + --server:requestCert "Set to true to specify whether a server should request a certificate from a connecting client" + --server:rejectUnauthorized "Set to true to automatically reject clients with invalid certificates" + +Documentation for SSL options: https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options + +Sample usages: + `ignite-web-console-win.exe --mail:auth:user "my_u...@gmail.com" --mail:auth:pass "my_password"` + `ignite-web-console-win.exe --server:port 11443 --server:ssl true --server:requestCert true --server:key "server.key" --server:cert "server.crt" --server:ca "ca.crt" --server:passphrase "my_password"` Advanced configuration of SMTP for Web Console. ------------------------------------- @@ -90,9 +114,11 @@ In case of non GMail SMTP server it may require to change options in "settings.j Troubleshooting ------------------------------------- 1. On Windows check that MongoDB is not blocked by Antivirus/Firewall/Smartscreen. -2. Root permission is required to bind to 80 port under Mac OS X and Linux, but you may always start Web Console on another port if you don't have such permission. +2. Root permission is required to bind to 80 port under macOS and Linux, but you may always start Web Console + on another port if you don't have such permission. 3. For extended debug output start Web Console as following: - On Linux execute command in terminal: `DEBUG=mongodb-* ./ignite-web-console-linux` - On Windows execute two commands in terminal: - `SET DEBUG=mongodb-*` - `ignite-web-console-win.exe` + On Linux execute command in terminal: `DEBUG=mongodb-* ./ignite-web-console-linux` + On macOS execute command in terminal: `DEBUG=mongodb-* ./ignite-web-console-macos` + On Windows execute two commands in terminal: + `SET DEBUG=mongodb-*` + `ignite-web-console-win.exe` http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/app/browsersHandler.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/app/browsersHandler.js b/modules/web-console/backend/app/browsersHandler.js index a7156c5..820c3d4 100644 --- a/modules/web-console/backend/app/browsersHandler.js +++ b/modules/web-console/backend/app/browsersHandler.js @@ -38,12 +38,12 @@ module.exports = { * @param {Socket} sock */ add(sock) { - const token = sock.request.user._id.toString(); + const key = sock.request.user._id.toString(); - if (this.sockets.has(token)) - this.sockets.get(token).push(sock); + if (this.sockets.has(key)) + this.sockets.get(key).push(sock); else - this.sockets.set(token, [sock]); + this.sockets.set(key, [sock]); return this.sockets.get(sock.request.user); } @@ -52,9 +52,9 @@ module.exports = { * @param {Socket} sock */ remove(sock) { - const token = sock.request.user.token; + const key = sock.request.user._id.toString(); - const sockets = this.sockets.get(token); + const sockets = this.sockets.get(key); _.pull(sockets, sock); @@ -62,17 +62,15 @@ module.exports = { } get(account) { - let sockets = this.sockets.get(account._id.toString()); + const key = account._id.toString(); + + let sockets = this.sockets.get(key); if (_.isEmpty(sockets)) - this.sockets.set(account._id.toString(), sockets = []); + this.sockets.set(key, sockets = []); return sockets; } - - demo(token) { - return _.filter(this.sockets.get(token), (sock) => sock.request._query.IgniteDemoMode === 'true'); - } } class BrowsersHandler { @@ -191,10 +189,10 @@ module.exports = { * @param {Object.<String, String>} params * @return {Promise.<T>} */ - executeOnNode(agent, demo, credentials, params) { + executeOnNode(agent, token, demo, credentials, params) { return agent .then((agentSock) => agentSock.emitEvent('node:rest', - {uri: 'ignite', demo, params: _.merge({}, credentials, params)})); + {uri: 'ignite', token, demo, params: _.merge({}, credentials, params)})); } registerVisorTask(taskId, taskCls, ...argCls) { @@ -222,7 +220,9 @@ module.exports = { const agent = this._agentHnd.agent(sock.request.user, demo, clusterId); - this.executeOnNode(agent, demo, credentials, params) + const token = sock.request.user.token; + + this.executeOnNode(agent, token, demo, credentials, params) .then((data) => cb(null, data)) .catch((err) => cb(this.errorTransformer(err))); }); @@ -282,7 +282,9 @@ module.exports = { const agent = this._agentHnd.agent(sock.request.user, demo, clusterId); - this.executeOnNode(agent, demo, credentials, exeParams) + const token = sock.request.user.token; + + this.executeOnNode(agent, token, demo, credentials, exeParams) .then((data) => { if (data.finished && !data.zipped) return cb(null, data.result); http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/app/settings.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/app/settings.js b/modules/web-console/backend/app/settings.js index 233566b..9c4d3c3 100644 --- a/modules/web-console/backend/app/settings.js +++ b/modules/web-console/backend/app/settings.js @@ -61,7 +61,7 @@ module.exports = { return v === 'true' || v === true; }; - return { + const settings = { agent: { dists: nconf.get('agent:dists') || dfltAgentDists }, @@ -69,15 +69,6 @@ module.exports = { server: { host: nconf.get('server:host') || dfltHost, port: _normalizePort(nconf.get('server:port') || dfltPort), - // eslint-disable-next-line eqeqeq - SSLOptions: _isTrue('server:ssl') && { - enable301Redirects: true, - trustXFPHeader: true, - key: fs.readFileSync(nconf.get('server:key')), - cert: fs.readFileSync(nconf.get('server:cert')), - passphrase: nconf.get('server:keyPassphrase') - }, - // eslint-disable-next-line eqeqeq disableSignup: _isTrue('server:disable:signup') }, mail, @@ -86,5 +77,60 @@ module.exports = { sessionSecret: nconf.get('server:sessionSecret') || 'keyboard cat', tokenLength: 20 }; + + // Configure SSL options. + if (_isTrue('server:ssl')) { + const sslOptions = { + enable301Redirects: true, + trustXFPHeader: true, + isServer: true + }; + + const setSslOption = (name, fromFile = false) => { + const v = nconf.get(`server:${name}`); + + const hasOption = !!v; + + if (hasOption) + sslOptions[name] = fromFile ? fs.readFileSync(v) : v; + + return hasOption; + }; + + const setSslOptionBoolean = (name) => { + const v = nconf.get(`server:${name}`); + + if (v) + sslOptions[name] = v === 'true' || v === true; + }; + + setSslOption('key', true); + setSslOption('cert', true); + setSslOption('ca', true); + setSslOption('passphrase'); + setSslOption('ciphers'); + setSslOption('secureProtocol'); + setSslOption('clientCertEngine'); + setSslOption('pfx', true); + setSslOption('crl'); + setSslOption('dhparam'); + setSslOption('ecdhCurve'); + setSslOption('maxVersion'); + setSslOption('minVersion'); + setSslOption('secureOptions'); + setSslOption('sessionIdContext'); + + setSslOptionBoolean('honorCipherOrder'); + setSslOptionBoolean('requestCert'); + setSslOptionBoolean('rejectUnauthorized'); + + // Special care for case, when user set password for something like "123456". + if (sslOptions.passphrase) + sslOptions.passphrase = sslOptions.passphrase.toString(); + + settings.server.SSLOptions = sslOptions; + } + + return settings; } }; http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/config/settings.json.sample ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/config/settings.json.sample b/modules/web-console/backend/config/settings.json.sample index aa20ab4..c16ba26 100644 --- a/modules/web-console/backend/config/settings.json.sample +++ b/modules/web-console/backend/config/settings.json.sample @@ -1,26 +1,31 @@ { - "server": { - "port": 3000, - "sessionSecret": "CHANGE ME", - "ssl": false, - "key": "serve/keys/test.key", - "cert": "serve/keys/test.crt", - "keyPassphrase": "password", - "disable": { - "signup": false - } - }, - "mongodb": { - "url": "mongodb://localhost/console" - }, - "mail": { - "service": "", - "from": "Some Company Web Console <some_username@some_company.com>", - "greeting": "Some Company Web Console", - "sign": "Kind regards,<br>Some Company Team", - "auth": { - "user": "some_username@some_company.com", - "pass": "" - } + "server": { + "port": 3000, + "sessionSecret": "CHANGE ME", + "ssl": false, + "key": "path to file with server.key", + "cert": "path to file with server.crt", + "ca": "path to file with ca.crt", + "passphrase": "password", + "ciphers": "ECDHE-RSA-AES128-GCM-SHA256", + "secureProtocol": "TLSv1_2_method", + "requestCert": false, + "rejectUnauthorized": false, + "disable": { + "signup": false } + }, + "mongodb": { + "url": "mongodb://localhost/console" + }, + "mail": { + "service": "gmail", + "from": "Some Company Web Console <some_username@some_company.com>", + "greeting": "Some Company Web Console", + "sign": "Kind regards,<br>Some Company Team", + "auth": { + "user": "some_username@some_company.com", + "pass": "CHANGE ME" + } + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/launch-tools.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/launch-tools.js b/modules/web-console/backend/launch-tools.js index 3870b0d..0d6114f 100644 --- a/modules/web-console/backend/launch-tools.js +++ b/modules/web-console/backend/launch-tools.js @@ -55,7 +55,11 @@ const _onError = (addr, error) => { */ const init = ([settings, apiSrv, agentsHnd, browsersHnd]) => { // Start rest server. - const srv = settings.server.SSLOptions ? https.createServer(settings.server.SSLOptions) : http.createServer(); + const sslOptions = settings.server.SSLOptions; + + console.log(`Starting ${sslOptions ? 'HTTPS' : 'HTTP'} server`); + + const srv = sslOptions ? https.createServer(sslOptions) : http.createServer(); srv.listen(settings.server.port, settings.server.host); http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/package.json ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/package.json b/modules/web-console/backend/package.json index fb12748..4399ae7 100644 --- a/modules/web-console/backend/package.json +++ b/modules/web-console/backend/package.json @@ -68,7 +68,7 @@ "passport-local-mongoose": "4.0.0", "passport.socketio": "3.7.0", "pkg": "4.3.1", - "socket.io": "1.7.3", + "socket.io": "2.1.1", "uuid": "3.1.0" }, "devDependencies": { http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/app.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/app.js b/modules/web-console/frontend/app/app.js index e1cbdbb..fd75ade 100644 --- a/modules/web-console/frontend/app/app.js +++ b/modules/web-console/frontend/app/app.js @@ -44,7 +44,6 @@ import './modules/configuration/configuration.module'; import './modules/getting-started/GettingStarted.provider'; import './modules/dialog/dialog.module'; import './modules/ace.module'; -import './modules/socket.module'; import './modules/loading/loading.module'; import servicesModule from './services'; // endignite @@ -177,7 +176,6 @@ export default angular.module('ignite-console', [ 'ngSanitize', 'ngMessages', // Third party libs. - 'btford.socket-io', 'dndLists', 'gridster', 'mgcrea.ngStrap', @@ -201,7 +199,6 @@ export default angular.module('ignite-console', [ 'ignite-console.input-dialog', 'ignite-console.user', 'ignite-console.branding', - 'ignite-console.socket', 'ignite-console.agent', 'ignite-console.nodes', 'ignite-console.demo', http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js b/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js index 294f955..896c02b 100644 --- a/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js +++ b/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js @@ -18,15 +18,17 @@ import {tap} from 'rxjs/operators'; export default class { - static $inject = ['AgentManager', 'ConnectedClustersDialog']; + static $inject = ['$scope', 'AgentManager', 'ConnectedClustersDialog']; connectedClusters = 0; /** + * @param $scope Angular scope. * @param {import('app/modules/agent/AgentManager.service').default} agentMgr * @param {import('../connected-clusters-dialog/service').default} connectedClustersDialog */ - constructor(agentMgr, connectedClustersDialog) { + constructor($scope, agentMgr, connectedClustersDialog) { + this.$scope = $scope; this.agentMgr = agentMgr; this.connectedClustersDialog = connectedClustersDialog; } @@ -40,7 +42,10 @@ export default class { $onInit() { this.connectedClusters$ = this.agentMgr.connectionSbj.pipe( tap(({ clusters }) => this.connectedClusters = clusters.length), - tap(({ clusters }) => this.clusters = clusters) + tap(({ clusters }) => { + this.clusters = clusters; + this.$scope.$applyAsync(); + }) ) .subscribe(); } http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/modules/agent/AgentManager.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js index 56eef35..0c0d9b6 100644 --- a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js +++ b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js @@ -21,6 +21,8 @@ import {nonEmpty, nonNil} from 'app/utils/lodashMixins'; import {BehaviorSubject} from 'rxjs'; import {first, pluck, tap, distinctUntilChanged, map, filter} from 'rxjs/operators'; +import io from 'socket.io-client'; + import AgentModal from './AgentModal.service'; // @ts-ignore import Worker from './decompress.worker'; @@ -117,7 +119,7 @@ class ConnectionState { } export default class AgentManager { - static $inject = ['$rootScope', '$q', '$transitions', 'igniteSocketFactory', 'AgentModal', 'UserNotifications', 'IgniteVersion', 'ClusterLoginService']; + static $inject = ['$rootScope', '$q', '$transitions', 'AgentModal', 'UserNotifications', 'IgniteVersion', 'ClusterLoginService']; /** @type {ng.IScope} */ $root; @@ -162,17 +164,15 @@ export default class AgentManager { * @param {ng.IRootScopeService} $root * @param {ng.IQService} $q * @param {import('@uirouter/angularjs').TransitionService} $transitions - * @param {unknown} socketFactory * @param {import('./AgentModal.service').default} agentModal * @param {import('app/components/user-notifications/service').default} UserNotifications * @param {import('app/services/Version.service').default} Version * @param {import('./components/cluster-login/service').default} ClusterLoginSrv */ - constructor($root, $q, $transitions, socketFactory, agentModal, UserNotifications, Version, ClusterLoginSrv) { + constructor($root, $q, $transitions, agentModal, UserNotifications, Version, ClusterLoginSrv) { this.$root = $root; this.$q = $q; this.$transitions = $transitions; - this.socketFactory = socketFactory; this.agentModal = agentModal; this.UserNotifications = UserNotifications; this.Version = Version; @@ -219,7 +219,9 @@ export default class AgentManager { if (nonNil(this.socket)) return; - this.socket = this.socketFactory(); + const options = this.isDemoMode() ? {query: 'IgniteDemoMode=true'} : {}; + + this.socket = io.connect(options); const onDisconnect = () => { const conn = this.connectionSbj.getValue(); http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/modules/demo/Demo.module.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/modules/demo/Demo.module.js b/modules/web-console/frontend/app/modules/demo/Demo.module.js index 9416bca..57f3a02 100644 --- a/modules/web-console/frontend/app/modules/demo/Demo.module.js +++ b/modules/web-console/frontend/app/modules/demo/Demo.module.js @@ -25,19 +25,15 @@ const DEMO_QUERY_STATE = {state: 'base.sql.notebook', params: {noteId: 'demo'}}; /** * @param {import('@uirouter/angularjs').StateProvider} $state * @param {ng.IHttpProvider} $http - * @param {unknown} socketFactory */ -export function DemoProvider($state, $http, socketFactory) { +export function DemoProvider($state, $http) { if (/(\/demo.*)/ig.test(location.pathname)) sessionStorage.setItem('IgniteDemoMode', 'true'); const enabled = sessionStorage.getItem('IgniteDemoMode') === 'true'; - if (enabled) { - socketFactory.set({query: 'IgniteDemoMode=true'}); - + if (enabled) $http.interceptors.push('demoInterceptor'); - } function service($root) { $root.IgniteDemoMode = enabled; @@ -50,7 +46,7 @@ export function DemoProvider($state, $http, socketFactory) { return this; } -DemoProvider.$inject = ['$stateProvider', '$httpProvider', 'igniteSocketFactoryProvider']; +DemoProvider.$inject = ['$stateProvider', '$httpProvider']; /** * @param {{enabled: boolean}} Demo @@ -178,11 +174,9 @@ function config($stateProvider) { config.$inject = ['$stateProvider']; angular -.module('ignite-console.demo', [ - 'ignite-console.socket' -]) -.config(config) -.provider('Demo', DemoProvider) -.factory('demoInterceptor', demoInterceptor) -.provider('igniteDemoInfo', igniteDemoInfoProvider) -.service('DemoInfo', DemoInfo); + .module('ignite-console.demo', []) + .config(config) + .provider('Demo', DemoProvider) + .factory('demoInterceptor', demoInterceptor) + .provider('igniteDemoInfo', igniteDemoInfoProvider) + .service('DemoInfo', DemoInfo); http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/utils/SimpleWorkerPool.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/utils/SimpleWorkerPool.js b/modules/web-console/frontend/app/utils/SimpleWorkerPool.js index 25c7ae4..b751dc3 100644 --- a/modules/web-console/frontend/app/utils/SimpleWorkerPool.js +++ b/modules/web-console/frontend/app/utils/SimpleWorkerPool.js @@ -83,7 +83,7 @@ export default class SimpleWorkerPool { worker.postMessage(task.data); if (this.__dbg) - console.timeEnd('Post message'); + console.timeEnd(`Post message[pool=${this._name}]`); } terminate() { http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/vendor.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/vendor.js b/modules/web-console/frontend/app/vendor.js index 90808be..e9fc8e9 100644 --- a/modules/web-console/frontend/app/vendor.js +++ b/modules/web-console/frontend/app/vendor.js @@ -22,7 +22,6 @@ import 'angular-animate'; import 'angular-sanitize'; import 'angular-strap'; import 'angular-strap/dist/angular-strap.tpl'; -import 'angular-socket-io'; import 'angular-messages'; import '@uirouter/angularjs'; http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/package.json ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/package.json b/modules/web-console/frontend/package.json index 9d848df..aa6e037 100644 --- a/modules/web-console/frontend/package.json +++ b/modules/web-console/frontend/package.json @@ -44,7 +44,6 @@ "angular-nvd3": "1.0.9", "angular-sanitize": "1.7.4", "angular-smart-table": "2.1.11", - "angular-socket-io": "0.7.0", "angular-strap": "2.3.12", "angular-translate": "2.18.1", "angular-tree-control": "0.2.28", @@ -60,7 +59,7 @@ "file-saver": "1.3.3", "font-awesome": "4.7.0", "jquery": "3.2.1", - "json-bigint": "0.2.3", + "json-bigint": "0.3.0", "jsondiffpatch": "0.2.5", "jszip": "3.1.5", "lodash": "4.17.11", @@ -71,7 +70,7 @@ "resize-observer-polyfill": "1.5.0", "roboto-font": "0.1.0", "rxjs": "6.3.3", - "socket.io-client": "1.7.3", + "socket.io-client": "2.1.1", "tf-metatags": "2.0.0" }, "devDependencies": { http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/webpack/webpack.dev.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/webpack/webpack.dev.js b/modules/web-console/frontend/webpack/webpack.dev.js index 3af6377..59e3d45 100644 --- a/modules/web-console/frontend/webpack/webpack.dev.js +++ b/modules/web-console/frontend/webpack/webpack.dev.js @@ -23,9 +23,11 @@ const commonCfg = require('./webpack.common'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const backendPort = process.env.BACKEND_PORT || 3000; -const devServerPort = process.env.PORT || 9000; -const devServerHost = process.env.HOST || '0.0.0.0'; +const backendUrl = process.env.BACKEND_URL || 'http://localhost:3000'; +const webpackDevServerHost = process.env.HOST || '0.0.0.0'; +const webpackDevServerPort = process.env.PORT || 9000; + +console.log(`Backend url: ${backendUrl}`); module.exports = merge(commonCfg, { mode: 'development', @@ -70,15 +72,18 @@ module.exports = merge(commonCfg, { inline: true, proxy: { '/socket.io': { - target: `http://localhost:${backendPort}`, - ws: true + target: backendUrl, + ws: true, + secure: false }, '/agents': { - target: `http://localhost:${backendPort}`, - ws: true + target: backendUrl, + ws: true, + secure: false }, '/api/*': { - target: `http://localhost:${backendPort}` + target: backendUrl, + secure: false } }, watchOptions: { @@ -86,7 +91,7 @@ module.exports = merge(commonCfg, { poll: 2000 }, stats: 'errors-only', - host: devServerHost, - port: devServerPort + host: webpackDevServerHost, + port: webpackDevServerPort } }); http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/pom.xml ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/pom.xml b/modules/web-console/web-agent/pom.xml index 2a4864b..705f7ab 100644 --- a/modules/web-console/web-agent/pom.xml +++ b/modules/web-console/web-agent/pom.xml @@ -43,7 +43,7 @@ <dependency> <groupId>io.socket</groupId> <artifactId>socket.io-client</artifactId> - <version>0.8.3</version> + <version>1.0.0</version> </dependency> <dependency> @@ -55,13 +55,13 @@ <dependency> <groupId>com.beust</groupId> <artifactId>jcommander</artifactId> - <version>1.48</version> + <version>1.58</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> - <version>3.7.0</version> + <version>3.12.0</version> </dependency> <dependency> @@ -112,6 +112,18 @@ <build> <finalName>ignite-web-agent-${project.version}</finalName> + <testResources> + <testResource> + <directory>src/test/java</directory> + <excludes> + <exclude>**/*.java</exclude> + </excludes> + </testResource> + <testResource> + <directory>src/test/resources</directory> + </testResource> + </testResources> + <plugins> <plugin> <artifactId>maven-jar-plugin</artifactId> http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java index bb2a8a2..1a919d0 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java @@ -17,7 +17,6 @@ package org.apache.ignite.console.agent; -import com.beust.jcommander.Parameter; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; @@ -28,6 +27,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; +import java.util.stream.Collectors; +import com.beust.jcommander.Parameter; import org.apache.ignite.internal.util.typedef.F; import static java.nio.charset.StandardCharsets.UTF_8; @@ -96,6 +97,51 @@ public class AgentConfiguration { private Boolean disableDemo; /** */ + @Parameter(names = {"-nks", "--node-key-store"}, + description = "Path to key store that will be used to connect to cluster") + private String nodeKeyStore; + + /** */ + @Parameter(names = {"-nkp", "--node-key-store-password"}, + description = "Optional password for node key store") + private String nodeKeyStorePass; + + /** */ + @Parameter(names = {"-nts", "--node-trust-store"}, + description = "Path to trust store that will be used to connect to cluster") + private String nodeTrustStore; + + /** */ + @Parameter(names = {"-ntp", "--node-trust-store-password"}, + description = "Optional password for node trust store") + private String nodeTrustStorePass; + + /** */ + @Parameter(names = {"-sks", "--server-key-store"}, + description = "Path to key store that will be used to connect to Web server") + private String srvKeyStore; + + /** */ + @Parameter(names = {"-skp", "--server-key-store-password"}, + description = "Optional password for server key store") + private String srvKeyStorePass; + + /** */ + @Parameter(names = {"-sts", "--server-trust-store"}, + description = "Path to trust store that will be used to connect to Web server") + private String srvTrustStore; + + /** */ + @Parameter(names = {"-stp", "--server-trust-store-password"}, + description = "Optional password for server trust store") + private String srvTrustStorePass; + + /** */ + @Parameter(names = {"-cs", "--cipher-suites"}, + description = "Optional comma-separated list of SSL cipher suites to be used to connect to server and cluster") + private List<String> cipherSuites; + + /** */ @Parameter(names = {"-h", "--help"}, help = true, description = "Print this help message") private Boolean help; @@ -219,6 +265,132 @@ public class AgentConfiguration { } /** + * @return Path to node key store. + */ + public String nodeKeyStore() { + return nodeKeyStore; + } + + /** + * @param nodeKeyStore Path to node key store. + */ + public void nodeKeyStore(String nodeKeyStore) { + this.nodeKeyStore = nodeKeyStore; + } + + /** + * @return Node key store password. + */ + public String nodeKeyStorePassword() { + return nodeKeyStorePass; + } + + /** + * @param nodeKeyStorePass Node key store password. + */ + public void nodeKeyStorePassword(String nodeKeyStorePass) { + this.nodeKeyStorePass = nodeKeyStorePass; + } + + /** + * @return Path to node trust store. + */ + public String nodeTrustStore() { + return nodeTrustStore; + } + + /** + * @param nodeTrustStore Path to node trust store. + */ + public void nodeTrustStore(String nodeTrustStore) { + this.nodeTrustStore = nodeTrustStore; + } + + /** + * @return Node trust store password. + */ + public String nodeTrustStorePassword() { + return nodeTrustStorePass; + } + + /** + * @param nodeTrustStorePass Node trust store password. + */ + public void nodeTrustStorePassword(String nodeTrustStorePass) { + this.nodeTrustStorePass = nodeTrustStorePass; + } + + /** + * @return Path to server key store. + */ + public String serverKeyStore() { + return srvKeyStore; + } + + /** + * @param srvKeyStore Path to server key store. + */ + public void serverKeyStore(String srvKeyStore) { + this.srvKeyStore = srvKeyStore; + } + + /** + * @return Server key store password. + */ + public String serverKeyStorePassword() { + return srvKeyStorePass; + } + + /** + * @param srvKeyStorePass Server key store password. + */ + public void serverKeyStorePassword(String srvKeyStorePass) { + this.srvKeyStorePass = srvKeyStorePass; + } + + /** + * @return Path to server trust store. + */ + public String serverTrustStore() { + return srvTrustStore; + } + + /** + * @param srvTrustStore Path to server trust store. + */ + public void serverTrustStore(String srvTrustStore) { + this.srvTrustStore = srvTrustStore; + } + + /** + * @return Server trust store password. + */ + public String serverTrustStorePassword() { + return srvTrustStorePass; + } + + /** + * @param srvTrustStorePass Server trust store password. + */ + public void serverTrustStorePassword(String srvTrustStorePass) { + this.srvTrustStorePass = srvTrustStorePass; + } + + /** + * @return SSL cipher suites. + */ + public List<String> cipherSuites() { + return cipherSuites; + } + + /** + * @param cipherSuites SSL cipher suites. + */ + public void cipherSuites(List<String> cipherSuites) { + this.cipherSuites = cipherSuites; + } + + /** * @return {@code true} If agent options usage should be printed. */ public Boolean help() { @@ -235,35 +407,81 @@ public class AgentConfiguration { props.load(reader); } - String val = (String)props.remove("tokens"); + String val = props.getProperty("tokens"); if (val != null) tokens(new ArrayList<>(Arrays.asList(val.split(",")))); - val = (String)props.remove("server-uri"); + val = props.getProperty("server-uri"); if (val != null) serverUri(val); - val = (String)props.remove("node-uri"); + val = props.getProperty("node-uri"); + // Intentionaly wrapped by ArrayList, for further maniulations. if (val != null) nodeURIs(new ArrayList<>(Arrays.asList(val.split(",")))); - val = (String)props.remove("node-login"); + val = props.getProperty("node-login"); if (val != null) nodeLogin(val); - val = (String)props.remove("node-password"); + val = props.getProperty("node-password"); if (val != null) nodePassword(val); - val = (String)props.remove("driver-folder"); + val = props.getProperty("driver-folder"); if (val != null) driversFolder(val); + + val = props.getProperty("node-key-store"); + + if (val != null) + nodeKeyStore(val); + + val = props.getProperty("node-key-store-password"); + + if (val != null) + nodeKeyStorePassword(val); + + val = props.getProperty("node-trust-store"); + + if (val != null) + nodeTrustStore(val); + + val = props.getProperty("node-trust-store-password"); + + if (val != null) + nodeTrustStorePassword(val); + + val = props.getProperty("server-key-store"); + + if (val != null) + serverKeyStore(val); + + val = props.getProperty("server-key-store-password"); + + if (val != null) + serverKeyStorePassword(val); + + val = props.getProperty("server-trust-store"); + + if (val != null) + serverTrustStore(val); + + val = props.getProperty("server-trust-store-password"); + + if (val != null) + serverTrustStorePassword(val); + + val = props.getProperty("cipher-suites"); + + if (val != null) + cipherSuites(Arrays.asList(val.split(","))); } /** @@ -296,43 +514,66 @@ public class AgentConfiguration { if (disableDemo == null) disableDemo(cfg.disableDemo()); + + if (nodeKeyStore == null) + nodeKeyStore(cfg.nodeKeyStore()); + + if (nodeKeyStorePass == null) + nodeKeyStorePassword(cfg.nodeKeyStorePassword()); + + if (nodeTrustStore == null) + nodeTrustStore(cfg.nodeTrustStore()); + + if (nodeTrustStorePass == null) + nodeTrustStorePassword(cfg.nodeTrustStorePassword()); + + if (srvKeyStore == null) + serverKeyStore(cfg.serverKeyStore()); + + if (srvKeyStorePass == null) + serverKeyStorePassword(cfg.serverKeyStorePassword()); + + if (srvTrustStore == null) + serverTrustStore(cfg.serverTrustStore()); + + if (srvTrustStorePass == null) + serverTrustStorePassword(cfg.serverTrustStorePassword()); + + if (cipherSuites == null) + cipherSuites(cfg.cipherSuites()); + } + + /** + * @param s String with sensitive data. + * @return Secured string. + */ + private String secured(String s) { + int len = s.length(); + int toShow = len > 4 ? 4 : 1; + + return new String(new char[len - toShow]).replace('\0', '*') + s.substring(len - toShow, len); } /** {@inheritDoc} */ @Override public String toString() { StringBuilder sb = new StringBuilder(); + String nl = System.lineSeparator(); + if (!F.isEmpty(tokens)) { sb.append("User's security tokens : "); - boolean first = true; - - for (String tok : tokens) { - if (first) - first = false; - else - sb.append(','); - - if (tok.length() > 4) { - sb.append(new String(new char[tok.length() - 4]).replace('\0', '*')); - - sb.append(tok.substring(tok.length() - 4)); - } - else - sb.append(new String(new char[tok.length()]).replace('\0', '*')); - } - - sb.append('\n'); + sb.append(tokens.stream().map(this::secured).collect(Collectors.joining(", "))).append(nl); } sb.append("URI to Ignite node REST server : ") - .append(nodeURIs == null ? DFLT_NODE_URI : String.join(", ", nodeURIs)).append('\n'); + .append(nodeURIs == null ? DFLT_NODE_URI : String.join(", ", nodeURIs)).append(nl); if (nodeLogin != null) - sb.append("Login to Ignite node REST server: ").append(nodeLogin).append('\n'); + sb.append("Login to Ignite node REST server: ").append(nodeLogin).append(nl); - sb.append("URI to Ignite Console server : ").append(srvUri == null ? DFLT_SERVER_URI : srvUri).append('\n'); - sb.append("Path to agent property file : ").append(configPath()).append('\n'); + sb.append("URI to Ignite Console server : ").append(srvUri == null ? DFLT_SERVER_URI : srvUri).append(nl); + sb.append("Path to agent property file : ").append(configPath()).append(nl); String drvFld = driversFolder(); @@ -343,8 +584,35 @@ public class AgentConfiguration { drvFld = new File(agentHome, "jdbc-drivers").getPath(); } - sb.append("Path to JDBC drivers folder : ").append(drvFld).append('\n'); - sb.append("Demo mode : ").append(disableDemo() ? "disabled" : "enabled"); + sb.append("Path to JDBC drivers folder : ").append(drvFld).append(nl); + sb.append("Demo mode : ").append(disableDemo() ? "disabled" : "enabled").append(nl); + + if (!F.isEmpty(nodeKeyStore)) + sb.append("Node key store : ").append(nodeKeyStore).append(nl); + + if (!F.isEmpty(nodeKeyStorePass)) + sb.append("Node key store password : ").append(secured(nodeKeyStorePass)).append(nl); + + if (!F.isEmpty(nodeTrustStore)) + sb.append("Node trust store : ").append(nodeTrustStore).append(nl); + + if (!F.isEmpty(nodeTrustStorePass)) + sb.append("Node trust store password : ").append(secured(nodeTrustStorePass)).append(nl); + + if (!F.isEmpty(srvKeyStore)) + sb.append("Server key store : ").append(srvKeyStore).append(nl); + + if (!F.isEmpty(srvKeyStorePass)) + sb.append("Server key store password : ").append(secured(srvKeyStorePass)).append(nl); + + if (!F.isEmpty(srvTrustStore)) + sb.append("Server trust store : ").append(srvTrustStore).append(nl); + + if (!F.isEmpty(srvTrustStorePass)) + sb.append("Server trust store password : ").append(secured(srvTrustStorePass)).append(nl); + + if (!F.isEmpty(cipherSuites)) + sb.append("Cipher suites : ").append(String.join(", ", cipherSuites)).append(nl); return sb.toString(); } http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java index 579f236..9553aac 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java @@ -17,12 +17,6 @@ package org.apache.ignite.console.agent; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.ParameterException; -import io.socket.client.Ack; -import io.socket.client.IO; -import io.socket.client.Socket; -import io.socket.emitter.Emitter; import java.io.File; import java.io.IOException; import java.net.Authenticator; @@ -40,9 +34,16 @@ import java.util.Scanner; import java.util.concurrent.CountDownLatch; import java.util.jar.Attributes; import java.util.jar.Manifest; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.TrustManager; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; +import io.socket.client.Ack; +import io.socket.client.IO; +import io.socket.client.Socket; +import io.socket.emitter.Emitter; +import okhttp3.OkHttpClient; import org.apache.ignite.console.agent.handlers.ClusterListener; import org.apache.ignite.console.agent.handlers.DatabaseListener; import org.apache.ignite.console.agent.handlers.RestListener; @@ -61,6 +62,8 @@ import static io.socket.client.Socket.EVENT_CONNECT_ERROR; import static io.socket.client.Socket.EVENT_DISCONNECT; import static io.socket.client.Socket.EVENT_ERROR; import static org.apache.ignite.console.agent.AgentUtils.fromJSON; +import static org.apache.ignite.console.agent.AgentUtils.sslConnectionSpec; +import static org.apache.ignite.console.agent.AgentUtils.sslSocketFactory; import static org.apache.ignite.console.agent.AgentUtils.toJSON; import static org.apache.ignite.console.agent.AgentUtils.trustManager; @@ -315,28 +318,75 @@ public class AgentLauncher { return; } + boolean trustAll = Boolean.getBoolean("trust.all"); + boolean hasServerTrustStore = cfg.serverTrustStore() != null; + boolean hasNodeTrustStore = cfg.nodeTrustStore() != null; + + if (trustAll && hasServerTrustStore) { + log.warn("Options contains both '--server-trust-store' and '-Dtrust.all=true'. " + + "Option '-Dtrust.all=true' will be ignored."); + + trustAll = false; + } + + if (trustAll && hasNodeTrustStore) { + log.warn("Options contains both '--node-trust-store' and '-Dtrust.all=true'. " + + "Option '-Dtrust.all=true' will be ignored."); + + trustAll = false; + } + cfg.nodeURIs(nodeURIs); IO.Options opts = new IO.Options(); - opts.path = "/agents"; - // Workaround for use self-signed certificate - if (Boolean.getBoolean("trust.all")) { - log.info("Trust to all certificates mode is enabled."); - - SSLContext ctx = SSLContext.getInstance("TLS"); + List<String> cipherSuites = cfg.cipherSuites(); + + if ( + trustAll || + hasServerTrustStore || + cfg.serverKeyStore() != null + ) { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + + X509TrustManager serverTrustMgr = trustManager( + trustAll, + cfg.serverTrustStore(), + cfg.serverTrustStorePassword() + ); + + SSLSocketFactory sslSocketFactory = sslSocketFactory( + cfg.serverKeyStore(), + cfg.serverKeyStorePassword(), + serverTrustMgr, + cipherSuites + ); + + if (sslSocketFactory != null) { + builder.sslSocketFactory(sslSocketFactory, serverTrustMgr); + + if (!F.isEmpty(cipherSuites)) + builder.connectionSpecs(sslConnectionSpec(cipherSuites)); + } - // Create an SSLContext that uses our TrustManager - ctx.init(null, new TrustManager[] {trustManager()}, null); + OkHttpClient sslFactory = builder.build(); - opts.sslContext = ctx; + opts.callFactory = sslFactory; + opts.webSocketFactory = sslFactory; + opts.secure = true; } final Socket client = IO.socket(uri, opts); - try (RestExecutor restExecutor = new RestExecutor(); - ClusterListener clusterLsnr = new ClusterListener(cfg, client, restExecutor)) { + try ( + RestExecutor restExecutor = new RestExecutor( + cfg.nodeKeyStore(), cfg.nodeKeyStorePassword(), + cfg.nodeTrustStore(), cfg.nodeTrustStorePassword(), + cipherSuites); + + ClusterListener clusterLsnr = new ClusterListener(cfg, client, restExecutor) + ) { Emitter.Listener onConnect = connectRes -> { log.info("Connection established."); @@ -416,6 +466,7 @@ public class AgentLauncher { }; DatabaseListener dbHnd = new DatabaseListener(cfg); + RestListener restHnd = new RestListener(cfg, restExecutor); final CountDownLatch latch = new CountDownLatch(1); http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java index 38edd97..2242eb1 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java @@ -17,16 +17,32 @@ package org.apache.ignite.console.agent; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule; -import io.socket.client.Ack; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; import java.security.ProtectionDomain; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule; +import io.socket.client.Ack; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import okhttp3.ConnectionSpec; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.ssl.SSLContextWrapper; import org.apache.log4j.Logger; import org.json.JSONArray; import org.json.JSONObject; @@ -38,6 +54,9 @@ public class AgentUtils { /** */ private static final Logger log = Logger.getLogger(AgentUtils.class.getName()); + /** */ + private static final char[] EMPTY_PWD = new char[0]; + /** JSON object mapper. */ private static final ObjectMapper MAPPER = new ObjectMapper(); @@ -50,7 +69,7 @@ public class AgentUtils { private static final Ack NOOP_CB = new Ack() { @Override public void call(Object... args) { if (args != null && args.length > 0 && args[0] instanceof Throwable) - log.error("Failed to execute request on agent.", (Throwable) args[0]); + log.error("Failed to execute request on agent.", (Throwable)args[0]); else log.info("Request on agent successfully executed " + Arrays.toString(args)); } @@ -186,13 +205,120 @@ public class AgentUtils { } /** - * Create a trust manager that trusts all certificates It is not using a particular keyStore + * @param pathToJks Path to java key store file. + * @param pwd Key store password. + * @return Key store. + * @throws GeneralSecurityException If failed to load key store. + * @throws IOException If failed to load key store file content. + */ + private static KeyStore keyStore(String pathToJks, char[] pwd) throws GeneralSecurityException, IOException { + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(new FileInputStream(pathToJks), pwd); + + return keyStore; + } + + /** + * @param keyStorePath Path to key store. + * @param keyStorePwd Key store password. + * @return Key managers. + * @throws GeneralSecurityException If failed to load key store. + * @throws IOException If failed to load key store file content. + */ + private static KeyManager[] keyManagers(String keyStorePath, String keyStorePwd) + throws GeneralSecurityException, IOException { + if (keyStorePath == null) + return null; + + char[] keyPwd = keyStorePwd != null ? keyStorePwd.toCharArray() : EMPTY_PWD; + + KeyStore keyStore = keyStore(keyStorePath, keyPwd); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, keyPwd); + + return kmf.getKeyManagers(); + } + + /** + * @param trustAll {@code true} If we trust to self-signed sertificates. + * @param trustStorePath Path to trust store file. + * @param trustStorePwd Trust store password. + * @return Trust manager + * @throws GeneralSecurityException If failed to load trust store. + * @throws IOException If failed to load trust store file content. + */ + public static X509TrustManager trustManager(boolean trustAll, String trustStorePath, String trustStorePwd) + throws GeneralSecurityException, IOException { + if (trustAll) + return disabledTrustManager(); + + if (trustStorePath == null) + return null; + + char[] trustPwd = trustStorePwd != null ? trustStorePwd.toCharArray() : EMPTY_PWD; + KeyStore trustKeyStore = keyStore(trustStorePath, trustPwd); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(trustKeyStore); + + TrustManager[] trustMgrs = tmf.getTrustManagers(); + + return (X509TrustManager)Arrays.stream(trustMgrs) + .filter(tm -> tm instanceof X509TrustManager) + .findFirst() + .orElseThrow(() -> new IllegalStateException("X509TrustManager manager not found")); + } + + /** + * Create SSL socket factory. + * + * @param keyStorePath Path to key store. + * @param keyStorePwd Key store password. + * @param trustMgr Trust manager. + * @param cipherSuites Optional cipher suites. + * @throws GeneralSecurityException If failed to load trust store. + * @throws IOException If failed to load store file content. + */ + public static SSLSocketFactory sslSocketFactory( + String keyStorePath, String keyStorePwd, + X509TrustManager trustMgr, + List<String> cipherSuites + ) throws GeneralSecurityException, IOException { + KeyManager[] keyMgrs = keyManagers(keyStorePath, keyStorePwd); + + if (keyMgrs == null && trustMgr == null) + return null; + + SSLContext ctx = SSLContext.getInstance("TLS"); + + if (!F.isEmpty(cipherSuites)) + ctx = new SSLContextWrapper(ctx, new SSLParameters(cipherSuites.toArray(new String[0]))); + + ctx.init(keyMgrs, new TrustManager[] {trustMgr}, null); + + return ctx.getSocketFactory(); + } + + /** + * Create SSL configuration. + * + * @param cipherSuites SSL cipher suites. + */ + public static List<ConnectionSpec> sslConnectionSpec(List<String> cipherSuites) { + return Collections.singletonList(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .cipherSuites(cipherSuites.toArray(new String[0])) + .build()); + } + + /** + * Create a trust manager that trusts all certificates. */ - public static X509TrustManager trustManager() { + private static X509TrustManager disabledTrustManager() { return new X509TrustManager() { /** {@inheritDoc} */ @Override public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; + return null; } /** {@inheritDoc} */ http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java index 33e4c2b..e0cd595 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java @@ -95,12 +95,17 @@ abstract class AbstractListener implements Emitter.Listener { } cb.call(null, toJSON(res)); - } catch (Exception e) { + } + catch (Throwable e) { + log.error("Failed to process event in pool", e); + cb.call(e, null); } }); } - catch (Exception e) { + catch (Throwable e) { + log.error("Failed to process event", e); + cb.call(e, null); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java index 14d3d5d..a9f1579 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java @@ -92,8 +92,8 @@ public class ClusterListener implements AutoCloseable { /** */ private static final String EVENT_CLUSTER_DISCONNECTED = "cluster:disconnected"; - /** Default timeout. */ - private static final long DFLT_TIMEOUT = 3000L; + /** Topology refresh frequency. */ + private static final long REFRESH_FREQ = 3000L; /** JSON object mapper. */ private static final ObjectMapper MAPPER = new GridJettyObjectMapper(); @@ -116,13 +116,13 @@ public class ClusterListener implements AutoCloseable { }; /** */ - private AgentConfiguration cfg; + private final AgentConfiguration cfg; /** */ - private Socket client; + private final Socket client; /** */ - private RestExecutor restExecutor; + private final RestExecutor restExecutor; /** */ private static final ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); @@ -132,7 +132,7 @@ public class ClusterListener implements AutoCloseable { /** * @param client Client. - * @param restExecutor Client. + * @param restExecutor REST executor. */ public ClusterListener(AgentConfiguration cfg, Socket client, RestExecutor restExecutor) { this.cfg = cfg; @@ -179,7 +179,7 @@ public class ClusterListener implements AutoCloseable { public void watch() { safeStopRefresh(); - refreshTask = pool.scheduleWithFixedDelay(watchTask, 0L, DFLT_TIMEOUT, TimeUnit.MILLISECONDS); + refreshTask = pool.scheduleWithFixedDelay(watchTask, 0L, REFRESH_FREQ, TimeUnit.MILLISECONDS); } /** {@inheritDoc} */ @@ -426,14 +426,15 @@ public class ClusterListener implements AutoCloseable { /** * Collect topology. * - * @param full Full. + * @return REST result. + * @throws IOException If failed to collect topology. */ - private RestResult topology(boolean full) throws IOException { - Map<String, Object> params = U.newHashMap(3); + private RestResult topology() throws IOException { + Map<String, Object> params = U.newHashMap(4); params.put("cmd", "top"); params.put("attr", true); - params.put("mtr", full); + params.put("mtr", false); params.put("caches", false); return restCommand(params); @@ -476,57 +477,47 @@ public class ClusterListener implements AutoCloseable { RestResult res = restCommand(params); - switch (res.getStatus()) { - case STATUS_SUCCESS: - if (v23) - return Boolean.valueOf(res.getData()); - - return res.getData().contains("\"active\":true"); + if (res.getStatus() == STATUS_SUCCESS) + return v23 ? Boolean.valueOf(res.getData()) : res.getData().contains("\"active\":true"); - default: - throw new IOException(res.getError()); - } + throw new IOException(res.getError()); } - /** {@inheritDoc} */ @Override public void run() { try { - RestResult res = topology(false); - - switch (res.getStatus()) { - case STATUS_SUCCESS: - List<GridClientNodeBean> nodes = MAPPER.readValue(res.getData(), - new TypeReference<List<GridClientNodeBean>>() {}); - - TopologySnapshot newTop = new TopologySnapshot(nodes); + RestResult res = topology(); - if (newTop.differentCluster(top)) - log.info("Connection successfully established to cluster with nodes: " + newTop.nid8()); - else if (newTop.topologyChanged(top)) - log.info("Cluster topology changed, new topology: " + newTop.nid8()); + if (res.getStatus() == STATUS_SUCCESS) { + List<GridClientNodeBean> nodes = MAPPER.readValue(res.getData(), + new TypeReference<List<GridClientNodeBean>>() {}); - boolean active = active(newTop.clusterVersion(), F.first(newTop.getNids())); + TopologySnapshot newTop = new TopologySnapshot(nodes); - newTop.setActive(active); - newTop.setSecured(!F.isEmpty(res.getSessionToken())); + if (newTop.differentCluster(top)) + log.info("Connection successfully established to cluster with nodes: " + newTop.nid8()); + else if (newTop.topologyChanged(top)) + log.info("Cluster topology changed, new topology: " + newTop.nid8()); - top = newTop; + boolean active = active(newTop.clusterVersion(), F.first(newTop.getNids())); - client.emit(EVENT_CLUSTER_TOPOLOGY, toJSON(top)); + newTop.setActive(active); + newTop.setSecured(!F.isEmpty(res.getSessionToken())); - break; + top = newTop; - default: - LT.warn(log, res.getError()); + client.emit(EVENT_CLUSTER_TOPOLOGY, toJSON(top)); + } + else { + LT.warn(log, res.getError()); - clusterDisconnect(); + clusterDisconnect(); } } catch (ConnectException ignored) { clusterDisconnect(); } - catch (Exception e) { + catch (Throwable e) { log.error("WatchTask failed", e); clusterDisconnect(); http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java index 24c2097..0db8904 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java @@ -24,6 +24,7 @@ import org.apache.ignite.console.agent.AgentConfiguration; import org.apache.ignite.console.agent.rest.RestExecutor; import org.apache.ignite.console.agent.rest.RestResult; import org.apache.ignite.console.demo.AgentClusterDemo; +import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; /** @@ -38,7 +39,7 @@ public class RestListener extends AbstractListener { /** * @param cfg Config. - * @param restExecutor Executor. + * @param restExecutor REST executor. */ public RestListener(AgentConfiguration cfg, RestExecutor restExecutor) { this.cfg = cfg; @@ -65,6 +66,9 @@ public class RestListener extends AbstractListener { boolean demo = (boolean)args.get("demo"); + if (F.isEmpty((String)args.get("token"))) + return RestResult.fail(401, "Request does not contain user token."); + Map<String, Object> headers = null; if (args.containsKey("headers")) http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java index d3bdcdd..b452b2c 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java @@ -20,11 +20,10 @@ package org.apache.ignite.console.agent.rest; import java.io.IOException; import java.io.StringWriter; import java.net.ConnectException; +import java.security.GeneralSecurityException; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; @@ -33,6 +32,8 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; import okhttp3.Dispatcher; import okhttp3.FormBody; import okhttp3.HttpUrl; @@ -41,6 +42,7 @@ import okhttp3.Request; import okhttp3.Response; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.processors.rest.protocols.http.jetty.GridJettyObjectMapper; +import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.LT; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.logger.slf4j.Slf4jLogger; @@ -49,6 +51,8 @@ import org.slf4j.LoggerFactory; import static com.fasterxml.jackson.core.JsonToken.END_ARRAY; import static com.fasterxml.jackson.core.JsonToken.END_OBJECT; import static com.fasterxml.jackson.core.JsonToken.START_ARRAY; +import static org.apache.ignite.console.agent.AgentUtils.sslConnectionSpec; +import static org.apache.ignite.console.agent.AgentUtils.sslSocketFactory; import static org.apache.ignite.console.agent.AgentUtils.trustManager; import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_AUTH_FAILED; import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_FAILED; @@ -68,14 +72,28 @@ public class RestExecutor implements AutoCloseable { private final OkHttpClient httpClient; /** Index of alive node URI. */ - private Map<List<String>, Integer> startIdxs = U.newHashMap(2); + private final Map<List<String>, Integer> startIdxs = U.newHashMap(2); /** - * Default constructor. + * Constructor. + * + * @param keyStorePath Optional path to key store file. + * @param keyStorePwd Optional password for key store. + * @param trustStorePath Optional path to trust store file. + * @param trustStorePwd Optional password for trust store. + * @param cipherSuites Optional cipher suites. + * @throws GeneralSecurityException If failed to initialize SSL. + * @throws IOException If failed to load content of key stores. */ - public RestExecutor() { + public RestExecutor( + String keyStorePath, + String keyStorePwd, + String trustStorePath, + String trustStorePwd, + List<String> cipherSuites + ) throws GeneralSecurityException, IOException { Dispatcher dispatcher = new Dispatcher(); - + dispatcher.setMaxRequests(Integer.MAX_VALUE); dispatcher.setMaxRequestsPerHost(Integer.MAX_VALUE); @@ -83,20 +101,19 @@ public class RestExecutor implements AutoCloseable { .readTimeout(0, TimeUnit.MILLISECONDS) .dispatcher(dispatcher); - // Workaround for use self-signed certificate - if (Boolean.getBoolean("trust.all")) { - try { - SSLContext ctx = SSLContext.getInstance("TLS"); + X509TrustManager trustMgr = trustManager(Boolean.getBoolean("trust.all"), trustStorePath, trustStorePwd); - // Create an SSLContext that uses our TrustManager - ctx.init(null, new TrustManager[] {trustManager()}, null); + SSLSocketFactory sslSocketFactory = sslSocketFactory( + keyStorePath, keyStorePwd, + trustMgr, + cipherSuites + ); - builder.sslSocketFactory(ctx.getSocketFactory(), trustManager()); + if (sslSocketFactory != null) { + builder.sslSocketFactory(sslSocketFactory, trustMgr); - builder.hostnameVerifier((hostname, session) -> true); - } catch (Exception ignored) { - LT.warn(log, "Failed to initialize the Trust Manager for \"-Dtrust.all\" option to skip certificate validation."); - } + if (!F.isEmpty(cipherSuites)) + builder.connectionSpecs(sslConnectionSpec(cipherSuites)); } httpClient = builder.build(); @@ -120,13 +137,10 @@ public class RestExecutor implements AutoCloseable { int status = holder.getSuccessStatus(); - switch (status) { - case STATUS_SUCCESS: - return RestResult.success(holder.getResponse(), holder.getSessionToken()); + if (status == STATUS_SUCCESS) + return RestResult.success(holder.getResponse(), holder.getSessionToken()); - default: - return RestResult.fail(status, holder.getError()); - } + return RestResult.fail(status, holder.getError()); } if (res.code() == 401) @@ -171,8 +185,20 @@ public class RestExecutor implements AutoCloseable { } } - /** */ - public RestResult sendRequest(List<String> nodeURIs, Map<String, Object> params, Map<String, Object> headers) throws IOException { + /** + * Send request to cluster. + * + * @param nodeURIs List of cluster nodes URIs. + * @param params Map with reques params. + * @param headers Map with reques headers. + * @return Response from cluster. + * @throws IOException If failed to send request to cluster. + */ + public RestResult sendRequest( + List<String> nodeURIs, + Map<String, Object> params, + Map<String, Object> headers + ) throws IOException { Integer startIdx = startIdxs.getOrDefault(nodeURIs, 0); int urlsCnt = nodeURIs.size(); @@ -196,7 +222,7 @@ public class RestExecutor implements AutoCloseable { return res; } catch (ConnectException ignored) { - LT.warn(log, "Failed to connect to cluster [url=" + nodeUrl + "]"); + LT.warn(log, "Failed connect to cluster [url=" + nodeUrl + "]"); } } @@ -274,10 +300,10 @@ public class RestExecutor implements AutoCloseable { } /** - * @param sesTokStr String representation of session token. + * @param sesTok String representation of session token. */ - public void setSessionToken(String sesTokStr) { - this.sesTok = sesTokStr; + public void setSessionToken(String sesTok) { + this.sesTok = sesTok; } }