Skip to content

Commit

Permalink
JMX authentication support added
Browse files Browse the repository at this point in the history
  • Loading branch information
zuazo committed May 5, 2013
1 parent 3f1794b commit 5a89346
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 10 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ Returns a `Client` object.
* `port` - JMX port number to connect to.
* `protocol` - Protocol to use (defaults to `"rmi"`).
* `urlPath` - JMX URL Path (defaults to `"/jndi/{protocol}://{host}:{port}/jmx{protocol}"`).
* `username` - JMX authentication username.
* `password` - JMX authentication password.

### Client.connect()

Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function createClient(options) {
port = options.port;
urlPath = options.urlPath;
}
return new Client(serviceOrHost, port, protocol, urlPath);
return new Client(serviceOrHost, port, protocol, urlPath, options.username, options.password);
}

module.exports = {
Expand Down
28 changes: 25 additions & 3 deletions lib/adapters/mbeanServerConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ MBeanServerConnection.prototype.close = function() {
};

MBeanServerConnection.prototype.connect = function(jmxServiceUrl) {

function GetCredentials(callback) {
var self = this;
credentials = java.newArray("java.lang.String", [ self.username, self.password ]);
java.newInstance("java.util.HashMap", function(err, map) {
if (checkError(err, self)) return;
if (typeof self.username === "string" && typeof self.password === "string") {
var JMXConnector = java.import("javax.management.remote.JMXConnector");
map.put(JMXConnector.CREDENTIALS, credentials, function(err, v) {
if (checkError(err, self)) return;
callback(map);
});
} else {
callback(map);
}
});
}

function GetMBeanServerConnection() {
var self = this;
if (self.mbeanServerConnection === null) {
Expand All @@ -50,9 +68,8 @@ MBeanServerConnection.prototype.connect = function(jmxServiceUrl) {
self.jmxServiceUrl = jmxServiceUrl;
}
if (self.jmxConnector === null) {
java.newInstance("java.util.HashMap", function(err, map) {
if (checkError(err, self)) return;
self.JMXConnectorFactory.connect(self.jmxServiceUrl, map, function(err, jmxConnector) {
GetCredentials.call(self, function(credentials) {
self.JMXConnectorFactory.connect(self.jmxServiceUrl, credentials, function(err, jmxConnector) {
if (checkError(err, self)) return;
self.jmxConnector = jmxConnector;
GetMBeanServerConnection.call(self);
Expand Down Expand Up @@ -130,5 +147,10 @@ MBeanServerConnection.prototype.setAttribute = function(name, attribute, callbac
});
};

MBeanServerConnection.prototype.setCredentials = function(username, password) {
this.username = username;
this.password = password;
};

module.exports = MBeanServerConnection;

5 changes: 4 additions & 1 deletion lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ var JavaJmx = require("./javaJmx"),
util = require("util"),
EventEmitter = require("events").EventEmitter;

function Client(serviceOrHost, port, protocol, urlPath) {
function Client(serviceOrHost, port, protocol, urlPath, username, password) {
var self = this;

function subscribeTo(obj) {
Expand All @@ -22,10 +22,13 @@ function Client(serviceOrHost, port, protocol, urlPath) {
self.javaJmx = new JavaJmx();
subscribeTo(self.javaJmx);
self.jmxServiceUrl = this.javaJmx.JmxServiceUrl(serviceOrHost, port, protocol, urlPath);
self.username = username;
self.password = password;
}
util.inherits(Client, EventEmitter);

Client.prototype.connect = function() {
this.javaJmx.setCredentials(this.username, this.password);
this.javaJmx.connect(this.jmxServiceUrl);
};

Expand Down
4 changes: 4 additions & 0 deletions lib/javaJmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,9 @@ JavaJmx.prototype.setAttribute = function(mbean, attributeName, value, callback)
});
};

JavaJmx.prototype.setCredentials = function(username, password) {
this.mbeanServerConnection.setCredentials(username, password);
}

module.exports = JavaJmx;

12 changes: 12 additions & 0 deletions test/adapters/mbeanServerConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ describe("MBeanServerConnection", function() {
assert.strictEqual(mbeanServerConnection.jmxServiceUrl, "uri2");
});

it("should generate authentication credentials when required", function(done) {
mbeanServerConnection.username = "username1";
mbeanServerConnection.password = "password2";
mbeanServerConnection.JMXConnectorFactory.connect = function(jmxServiceUrl, map, callback) {
var credentials = map.getSync("jmx.remote.credentials");
assert.strictEqual(credentials[0], "username1");
assert.strictEqual(credentials[1], "password2");
done();
};
mbeanServerConnection.connect("uri2");
});

});

describe("#disconnect", function() {
Expand Down
6 changes: 6 additions & 0 deletions test/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ describe("Client", function() {
);
});

it("should accept username and password as arguments", function() {
var client = new Client("localhost", 3000, undefined, undefined, "username", "password");
assert.strictEqual(client.username, "username");
assert.strictEqual(client.password, "password");
});

describe("should subscribe to JavaJmx events", function() {
var client;
var emitted;
Expand Down
69 changes: 67 additions & 2 deletions test/integration.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
var assert = require("assert"),
chmod = require("fs").chmod,
jmx = require("./../index.js"),
StartJmxApp = require("./integration/startJmxApp.js");

var jmxPort = 63120;

describe("Integration tests", function() {
this.timeout(5000);
before(function(done) {
chmod(__dirname + "/integration/jmxremote.password", 0400, function(err) {
if (err) {
console.error(err);
}
done();
});
});

it("should run java JMX test app", function(done) {
var jmxApp = new StartJmxApp(jmxPort, function() {
var jmxApp = new StartJmxApp(jmxPort, null, function() {
jmxApp.stop(function() {
done();
});
Expand All @@ -18,7 +27,7 @@ describe("Integration tests", function() {
describe("client", function() {
var jmxApp;
before(function(done) {
jmxApp = new StartJmxApp(jmxPort, function() {
jmxApp = new StartJmxApp(jmxPort, null, function() {
done();
});
});
Expand Down Expand Up @@ -124,5 +133,61 @@ describe("Integration tests", function() {

});

it("should run java JMX test app with authentication enabled", function(done) {
var jmxApp = new StartJmxApp(jmxPort, "jmxremote.password", function() {
jmxApp.stop(function() {
done();
});
});
});

describe("starting a server with authentication", function() {
var jmxApp;
before(function(done) {
jmxApp = new StartJmxApp(jmxPort, "jmxremote.password", function() {
done();
});
});
after(function(done) {
jmxApp.stop(function() {
done();
});
});

it("should not connect succefully without credentials", function(done) {
client = jmx.createClient({
host: '127.0.0.1',
port: jmxPort
});
client.emit = function(ev, data) {
if (ev === "error") {
if (/java\.lang\.SecurityException/.test(data)) {
done();
} else {
console.error(data);
}
}
};
client.connect();
client.on("connect", function() {
assert(false, "connect");
});
});

it("should connect succefully with the correct credentials", function(done) {
client = jmx.createClient({
host: '127.0.0.1',
port: jmxPort,
username: "controlRole",
password: "testPassword"
});
client.connect();
client.on("connect", function() {
done();
});
});

});

});

1 change: 1 addition & 0 deletions test/integration/jmxremote.password
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
controlRole testPassword
11 changes: 8 additions & 3 deletions test/integration/startJmxApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ var jmxAppName = "JmxAppExample",
jmxPort = 3000,
jmxEnableArgs = [
"-Dcom.sun.management.jmxremote",
"-Dcom.sun.management.jmxremote.ssl=false",
"-Dcom.sun.management.jmxremote.authenticate=false"
"-Dcom.sun.management.jmxremote.ssl=false"
];

function StartJmxApp(port, done) {
function StartJmxApp(port, password_file, done) {
var self = this;

function onData(data) {
Expand All @@ -25,6 +24,12 @@ function StartJmxApp(port, done) {

var args = jmxEnableArgs.slice();
args.push("-Dcom.sun.management.jmxremote.port=" + port);
if (password_file) {
args.push("-Dcom.sun.management.jmxremote.password.file=" + password_file);
args.push("-Dcom.sun.management.jmxremote.authenticate=true");
} else {
args.push("-Dcom.sun.management.jmxremote.authenticate=false");
}
args.push(jmxAppName);
this.jmxApp = spawn("java", args, {
cwd: __dirname,
Expand Down
10 changes: 10 additions & 0 deletions test/javaJmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ describe("JavaJmx", function() {
});
});

it("#setCredentials", function(done) {
javaJmx.mbeanServerConnection.setCredentials = function(username, password, undef) {
assert.strictEqual(username, "username");
assert.strictEqual(password, "password");
assert.strictEqual(undef, undefined);
done();
};
javaJmx.setCredentials("username", "password");
});

describe("#invoke", function() {

it ("should accept a callback as the fourth parameter", function(done) {
Expand Down

0 comments on commit 5a89346

Please sign in to comment.