Skip to content

Add support for IAM authentication #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Configure the plugin separately for each container when using the docker run com
docker run --rm --label x \
--log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \
--log-opt journal-fqdn=5d78e1427fd7e0228fe18f46.journal.pl-waw-1.hyperone.cloud \
--log-opt journal-token=test \
--log-opt journal-password=test \
-it alpine id
```

Expand All @@ -65,7 +65,11 @@ Each message has the following tags assigned by default. The user has the abilit
### Required variables

* ```journal-fqdn``` – Journal FQDN that will receive logs
* ```journal-token``` – Credential (password) to journal indicated in the parameter ```journal-fqdn```

* access data to journal indicated in the parameter ```journal-fqdn``` (one of the following):
* password – ```journal-password``` – Credential (password) of journal
* service account – ```journal-passport``` – Content of service account passport file
* ```journal-credential-endpoint``` – credential endpoint eg. metadata service

### Optional variables

Expand All @@ -74,6 +78,7 @@ Each message has the following tags assigned by default. The user has the abilit
* ```env-regex``` – A regular expression to match logging-related environment variables. Used for advanced log tag options. If there is collision between the label and env keys, env wins. Disabled by default.
* ```flush-buffer-size``` – How many pending messages can be collected before sending to journal immediately. Default: 500
* ```flush-interval``` – How long (in miliseconds) the buffer keeps messages before flushing them. Default: 15000
* ```journal-unsecure``` – Use unsecure (HTTP) connection to Journal

## Development

Expand Down
10 changes: 4 additions & 6 deletions driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,9 @@ module.exports = () => {
const driver = {};

driver.startLogging = async (stream, File, Info) => {
['journal-fqdn', 'journal-token'].forEach(name => {
if (!Info.Config[name]) {
throw new Error(`Missing '${name} option of log driver`);
}
});
if (!Info.Config['journal-fqdn']) {
throw new Error('Missing \'journal-fqdn\' option of log driver');
}

let flush_interval = 15000;
try {
Expand Down Expand Up @@ -92,7 +90,7 @@ module.exports = () => {
await log.client.checkJournalToken();
} catch (err) {
console.error(err);
throw new Error('Invalid journal-token');
throw new Error(`Invalid/missing access data for journal: ${err}`);
}

log.interval = setInterval(flushLogBuffer, flush_interval, log);
Expand Down
110 changes: 87 additions & 23 deletions journal.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,88 @@
'use strict';
const qs = require('qs');
const logger = require('superagent-logger');
const jwt = require('jsonwebtoken');
const WebSocket = require('ws');
const { FilterJournalDockerStream, ParseJournalStream } = require('./transform');

const timeskew = 20;

module.exports = (config) => {
const url = `https://${config['journal-fqdn']}/log`;
const proto = config['journal-unsecure'] ? 'http' : 'https';
const url = `${proto}://${config['journal-fqdn']}/log`;
const agent = require('superagent').agent().use(logger);

let cred_req;
let expiresAt = 0;

const get_headers = async () => {
if (config['journal-password']) {
return { 'x-auth-password': config['journal-password'] };
}

if (config['journal-passport']) {
const passport = JSON.parse(config['journal-passport'].trim());
const token = jwt.sign({}, passport.private_key, {
algorithm: 'RS256',
expiresIn: '5m',
keyid: passport.certificate_id,
audience: config['journal-fqdn'],
issuer: passport.issuer,
subject: passport.subject_id,
});

return {
Authorization: `Bearer ${token}`,
};
}

if (config['journal-credential-endpoint']) {
const ts = Math.round(new Date().getTime() / 1000);
if (!cred_req) { // no credential
cred_req = agent.post(config['journal-credential-endpoint'])
.set({ Metadata: 'true' })
.send({ audience: config['journal-fqdn'] })
.then(resp => {
expiresAt = ts + resp.body.expires_in - timeskew;
const until = new Date(expiresAt * 1000).toISOString();
console.log(`Access token refreshed. Valid until ${until}.`);
return resp.body;
});
}
const credential = await cred_req;
// expired response
const exp = new Date(expiresAt * 1000).toISOString();
if (expiresAt < ts) {
cred_req = undefined;
console.log(`Access token is old. Expired at ${exp}. Refreshing.`);
console.log(`Refreshing token for ${config['journal-fqdn']}`);
return get_headers();
}
console.log(`Access token is fresh. Valid until ${exp}. Re-use.`);

const token = credential.access_token;

return {
Authorization: `Bearer ${token}`,
};
}

return {};
};

return {
checkJournalToken: () => agent
checkJournalToken: async () => agent
.head(url)
.query({ follow: 'false' })
.set('x-auth-password', config['journal-token']),
send: (messages) => new Promise((resolve, reject) => {
.set(await get_headers()),
send: async (messages) => {
const body = Array.isArray(messages) ? messages : [messages];
const content = body.map(x => JSON.stringify(x)).join('\n');
return agent
.post(url)
.send(content)
.set('x-auth-password', config['journal-token'])
.then(resolve)
.catch(reject);
}),
.set(await get_headers());
},
read: (read_config, read_info) => new Promise((resolve, reject) => {
const query = {
follow: read_config.Follow,
Expand All @@ -42,23 +103,26 @@ module.exports = (config) => {
console.log('query', query);
const ws_url = `${url}?${qs.stringify(query)}`;
console.log('WS', ws_url);
const ws = new WebSocket(ws_url, {
headers: { 'x-auth-password': config['journal-token'] },
});
return get_headers().then(headers => {
const ws = new WebSocket(ws_url, {
headers,
});

ws.on('open', () => {
console.log(config['journal-fqdn'], 'websocket opened');
const stream = WebSocket.createWebSocketStream(ws).
pipe(new ParseJournalStream()).
pipe(new FilterJournalDockerStream());
stream.pause();
resolve(stream);
});
ws.on('open', () => {
console.log(config['journal-fqdn'], 'websocket opened');
const stream = WebSocket.createWebSocketStream(ws).
pipe(new ParseJournalStream()).
pipe(new FilterJournalDockerStream());
stream.pause();
resolve(stream);
});

ws.on('close', () => {
console.log(config['journal-fqdn'], 'websocket closed');
});
ws.on('error', reject);
}).catch(resolve);

ws.on('close', () => {
console.log(config['journal-fqdn'], 'websocket closed');
});
ws.on('error', reject);
}),
};
};
94 changes: 92 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"co-body": "^6.0.0",
"dayjs": "^1.8.23",
"jsonwebtoken": "^8.5.1",
"koa": "^2.11.0",
"koa-body": "^4.1.1",
"koa-logger": "^3.2.1",
Expand All @@ -26,5 +27,6 @@
"@hyperone/eslint-config": "^2.0.1",
"ava": "^3.5.1",
"eslint": "^6.8.0"
}
},
"eslintIgnore": ["rootfs"]
}
2 changes: 1 addition & 1 deletion tests/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class LogGenerator extends Readable {
const defaultInfo = () => ({
Config: {
'journal-fqdn': `${process.env.JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud`,
'journal-token': process.env.JOURNAL_TOKEN,
'journal-password': process.env.JOURNAL_TOKEN,
},
ContainerID: getRandom(),
ContainerName: '/confident_carson',
Expand Down
Loading