Skip to content

Commit

Permalink
docker support for external key manager and verbosity configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
Robin Gottfried committed May 15, 2020
1 parent 32bedeb commit 9467292
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 20 deletions.
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ FROM node:13-alpine
ENV ADDRESS 0.0.0.0
ENV PORT 8090
ENV KEY_SERVER_URL ''
ENV KEY_SERVER_IGNORE_FOR_HOSTNAMES ''
ENV VERBOSITY 3

# Create app directory
WORKDIR /usr/src/app
Expand Down
1 change: 1 addition & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ let CONFIG = {
// empty value means no authenticator (allow any key to connect)
// http(s)://... - a url for key-master APIs
keyServerUrl: val('KEY_SERVER_URL', undefined),
keyServerIgoreForHostnames: val('KEY_SERVER_IGNORE_FOR_HOSTNAMES', undefined),
},
client: {
key: val('KEY', 'client-1'),
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "karmen_ws",
"version": "1.0.1",
"version": "1.0.2",
"description": "",
"main": "client",
"scripts": {
Expand Down
37 changes: 31 additions & 6 deletions server/api-authenticator.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';
// vim: ft=javascript tabstop=2 softtabstop=2 expandtab shiftwidth=2
const http = require('http'),
path = require('path');
path = require('path'),
{ debug, info } = require('../lib/logger');

class InvalidKey extends Error { }
exports.InvalidKey = InvalidKey;
Expand Down Expand Up @@ -30,11 +31,35 @@ function fetchJson(url, options) {
}


exports.getAuthenticator = function getAuthenticator(authenticatorUri) {
exports.getAuthenticator = function getAuthenticator(authenticatorUri, privateHostnamesToPass) {
if (!authenticatorUri) throw new Error(`authenticatorUri is a required parameter`);
return (key) => {
return fetchJson(
path.join(authenticatorUri, '/key', key)
);
if (privateHostnamesToPass) {
if (typeof privateHostnamesToPass == 'string') privateHostnamesToPass = privateHostnamesToPass.split(',');
if (!(privateHostnamesToPass instanceof Set)) privateHostnamesToPass = new Set(privateHostnamesToPass);
debug(`Set hosts to skip auth: ${[...privateHostnamesToPass].join(', ')}.`);
} else {
privateHostnamesToPass = null;
}
return (key, request) => {
let result;
if (privateHostnamesToPass && privateHostnamesToPass.has(request.headers.host)) {
debug(`Passing user with key ${key}, hostname '${request.headers.host}' matches one of ${[...privateHostnamesToPass].join(',')}.`);
result = Promise.resolve({authenticated: false});
} else {
debug(`Authenticating ${key} from ${request.headers.host} to ${authenticatorUri}.`);
result = fetchJson(
path.join(authenticatorUri, '/key', key)
)
.then((response) => {
debug(`Key ${key} authenticated.`);
return {authenticated: true, token: response}
})
.catch((err) => {
debug(`Key ${key} authentication failed: ${err}.`);
throw err;
});

}
return result;
}
}
10 changes: 6 additions & 4 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ const { getAuthenticator } = require('./api-authenticator');
`*
*/

function Server(keyServerUrl) {
function Server({keyServerUrl=null, keyServerIgoreForHostnames=null}={}) {

let authenticate;
if (keyServerUrl) {
authenticate = getAuthenticator(keyServerUrl);
authenticate = getAuthenticator(keyServerUrl, keyServerIgoreForHostnames);
}

this.clientsManager = new ClientsManager({
Expand Down Expand Up @@ -86,7 +86,9 @@ if (require.main == module) {
debug(`
config: ${server_host}:${server_port}
`);

new Server(config.server.keyServerUrl).listen(server_port, server_host);
new Server({
keyServerUrl: config.server.keyServerUrl,
keyServerIgoreForHostnames: config.server.keyServerIgoreForHostnames}
).listen(server_port, server_host);

}
37 changes: 30 additions & 7 deletions tests/authenticator.test.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,59 @@
// vim: ft=javascript tabstop=2 softtabstop=2 expandtab shiftwidth=2
const test = require('ava'),
nock = require('nock'),
authenticator = require('../server/api-authenticator');
authenticator = require('../server/api-authenticator'),
fakeRequest = {headers: {host: 'http://example.com'}};

test('valid key passes', t => {
const key = 'some-secret-key',
authServerUri = 'http://example.com',
response = {sub: 'userinfo', iss: 'iss'};
nock(`${authServerUri}`).get(`/key/${key}`).reply(200, response);
nock(authServerUri).get(`/key/${key}`).reply(200, response);
const authenticate = authenticator.getAuthenticator(authServerUri);
return authenticate(key).then(clientInfo => {
t.deepEqual(clientInfo, response);
return authenticate(key, fakeRequest).then(clientInfo => {
t.deepEqual(clientInfo, {authenticated: true, token: response});
});
});

test('auth is skipped for selected hostnames even when the key is invalid', t => {
const key = 'some-secret-key',
authServerUri = 'http://example.com',
authenticate = authenticator.getAuthenticator(authServerUri, 'a.example.com,b.example.com'),
request = {headers: {host: 'b.example.com'}},
promises = [];

promises.push(
authenticate(key, request).then(clientInfo => {
t.is(clientInfo.authenticated, false);
})
);
// nock(authServerUri).get(`/key/${key}`).reply(422);
// promises.push(
// authenticate(key, fakeRequest).catch((err) => {
// t.assert(err instanceof authenticator.InvalidKey)
// })
// );
return Promise.all(promises);
});

test('fail on invalid key', t => {
const key = 'some-secret-key',
authServerUri = 'http://example.com';
nock(`${authServerUri}`).get(`/key/${key}`).reply(422);
const authenticate = authenticator.getAuthenticator(authServerUri);
return authenticate(key).catch((err) => {
const authenticate = authenticator.getAuthenticator(authServerUri, 'a.example.com,b.example.com');
return authenticate(key, fakeRequest).catch((err) => {
t.assert(err instanceof authenticator.InvalidKey)
});
});



test('authentication rejected on server error', t => {
const key = 'some-secret-key',
authServerUri = 'http://example.com';
nock(`${authServerUri}`).get(`/key/${key}`).reply(500);
const authenticate = authenticator.getAuthenticator(authServerUri);
return authenticate(key).catch((err) => {
return authenticate(key, fakeRequest).catch((err) => {
t.assert(!(err instanceof authenticator.InvalidKey))
});
});
8 changes: 6 additions & 2 deletions tests/server-api-authentication.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test('connection is closed with wrong key', t => {
return client.connect(`ws+unix://${socketFilePath}:`, clientConfig)
.catch((err) => {
t.is(err.code, 'ECONNRESET');
});
})
});

test('connection accepted with a valid key', t => {
Expand All @@ -34,8 +34,12 @@ test.before(t => {
invalidKey = 'some-invalid-key',
validKeyResponse = {sub: 'userinfo', iss: 'iss'},
authServerUri = 'http://authserveruri.example.com',
keyServerIgoreForHostnames = 'a.example.com,b.example.com',
Server = require('../server'),
server = new Server(authServerUri);
server = new Server({
keyServerUrl: authServerUri,
keyServerIgoreForHostnames: keyServerIgoreForHostnames,
});

nock(`${authServerUri}`).persist().get(`/key/${validKey}`).reply(200, validKeyResponse);
nock(`${authServerUri}`).persist().get(`/key/${invalidKey}`).reply(422);
Expand Down

0 comments on commit 9467292

Please sign in to comment.