From b68b274fa0ea44322ccb0e0d5385438d087caaea Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Wed, 25 Mar 2020 23:14:54 +0100 Subject: [PATCH 1/9] Add support for IAM authentication --- README.md | 13 +++++- driver.js | 10 ++--- journal.js | 98 +++++++++++++++++++++++++++++++++++----------- package-lock.json | 94 +++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + tests/driver.js | 2 +- tests/e2e/e2e.bats | 10 ++--- tests/util.js | 2 +- 8 files changed, 190 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 3739a58..2ee3bb9 100644 --- a/README.md +++ b/README.md @@ -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 ``` @@ -65,7 +65,15 @@ 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``` + +* credential source (one of the following): + * password: + * ```journal-password``` – Credential (password) to journal indicated in the parameter ```journal-fqdn``` + * service account: + * ```journal-sa-id``` – eg. ```/iam/sa/project/5e7b89edd93046f509ca38d7/sa/5e7b89edd93046f509ca38d7``` + * ```journal-sa-kid``` – ID of certificate of service account eg. ```5e7b89edd93046f509ca38d7``` + * ```journal-private-key``` – private key of certificate added to actor + * ```journal-credential-endpoint``` – credential endpoint eg. metadata service ### Optional variables @@ -74,6 +82,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 diff --git a/driver.js b/driver.js index 37367bd..5a9b8e4 100644 --- a/driver.js +++ b/driver.js @@ -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 { @@ -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'); } log.interval = setInterval(flushLogBuffer, flush_interval, log); diff --git a/journal.js b/journal.js index fb58a51..18a8885 100644 --- a/journal.js +++ b/journal.js @@ -1,27 +1,76 @@ '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 API_URL = 'https://api.hyperone.com/v2'; + 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; + + const get_headers = async () => { + if (config['journal-password']) { + return { 'x-auth-password': config['journal-password'] }; + } + + if (config['journal-sa-id'] && config['journal-sa-kid'] && config['journal-private-key']) { + const token = jwt.sign({}, config['journal-private-key'], { + algorithm: 'RS256', + expiresIn: '5m', + keyid: config['journal-sa-kid'], + audience: config['journal-fqdn'], + issuer: `${API_URL}${config['journal-sa-id']}`, + subject: config['journal-sa-id'], + }); + + return { + Authorization: `Bearer ${token}`, + }; + } + + if (config['journal-credential-endpoint']) { + if (!cred_req) { // no credential + cred_req = agent.post(config['journal-credential-endpoint']) + .set({ Metadata: 'true' }) + .send({ audience: config['journal-fqdn'] }); + } + const cred_resp = await cred_req; + const ts = Math.round(new Date().getTime() / 1000); + // expired response + if (cred_resp.body.expires_on <= ts - 30) { + cred_req = undefined; + console.log(`Refreshing token for ${config['journal-fqdn']}`); + return get_headers(); + } + const token = cred_resp.body.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, @@ -42,23 +91,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); }), }; }; diff --git a/package-lock.json b/package-lock.json index 95fa1b5..c6fc1ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -548,6 +548,11 @@ "fill-range": "^7.0.1" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1075,6 +1080,14 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2048,6 +2061,49 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -2255,18 +2311,53 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, "lodash.islength": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.islength/-/lodash.islength-4.0.1.tgz", "integrity": "sha1-Tpho1FJXXXUK/9NYyXlUPcIO1Xc=", "dev": true }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -3088,8 +3179,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-diff": { "version": "3.1.1", diff --git a/package.json b/package.json index bf7313a..eafe99a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/tests/driver.js b/tests/driver.js index 5c65616..acbc94e 100644 --- a/tests/driver.js +++ b/tests/driver.js @@ -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', diff --git a/tests/e2e/e2e.bats b/tests/e2e/e2e.bats index 48b7894..9780db2 100644 --- a/tests/e2e/e2e.bats +++ b/tests/e2e/e2e.bats @@ -19,7 +19,7 @@ teardown() { --label dockerbats="$BATS_TEST_NAME" \ --log-opt labels=dockerbats \ --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ - --log-opt journal-token=${JOURNAL_TOKEN} \ + --log-opt journal-password=${JOURNAL_TOKEN} \ alpine sh -c 'echo $RANDOM'; [ "$status" -eq 0 ]; containerId=$(docker container ls -a -q --filter label=dockerbats="$BATS_TEST_NAME"); @@ -34,7 +34,7 @@ teardown() { --label dockerbats="$BATS_TEST_NAME" \ --log-opt labels=dockerbats \ --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ - --log-opt journal-token=${JOURNAL_TOKEN} \ + --log-opt journal-password=${JOURNAL_TOKEN} \ alpine sh -c 'seq 1 10; sleep 30'; [ "$status" -eq 0 ]; # Wait for flush (15 second default) @@ -52,7 +52,7 @@ teardown() { --label dockerbats="$BATS_TEST_NAME-${token}" \ --log-opt labels=dockerbats \ --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ - --log-opt journal-token=${JOURNAL_TOKEN} \ + --log-opt journal-password=${JOURNAL_TOKEN} \ alpine sh -c "seq 100 | while read line; do echo \"multiple-\${line}-${token}\"; done;"; [ "$status" -eq 0 ] containerId=$(docker container ls -a -q --filter label=dockerbats="${BATS_TEST_NAME}-${token}"); @@ -79,8 +79,8 @@ teardown() { --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ --label dockerbats="$BATS_TEST_NAME" \ --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ - --log-opt journal-token="invalid token" \ + --log-opt journal-password="invalid token" \ alpine id; - [[ $output =~ "Invalid journal-token." ]] + [[ $output =~ "Invalid journal-password." ]] [ "$status" -eq 125 ] } \ No newline at end of file diff --git a/tests/util.js b/tests/util.js index 04965e3..14b58d0 100644 --- a/tests/util.js +++ b/tests/util.js @@ -7,7 +7,7 @@ const demo = { Info: { Config: { 'journal-fqdn': '5d78e1427fd7e0228fe18f46.journal.pl-waw-1.hyperone.cloud', - 'journal-token': 'x', + 'journal-password': 'x', }, ContainerID: '956d79af66ec1e85cc409d1153af23ace3b2b55a6fdfa2dc39cd80ff8e7416bf', ContainerName: '/xenodochial_jang', From b31ab6e710398e492bd634fb53cf757fe7d5c78f Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Wed, 25 Mar 2020 23:23:15 +0100 Subject: [PATCH 2/9] Fix tests --- README.md | 6 +++--- driver.js | 2 +- journal.js | 2 +- tests/e2e/e2e.bats | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2ee3bb9..bc1f40b 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,11 @@ Each message has the following tags assigned by default. The user has the abilit * ```journal-fqdn``` – Journal FQDN that will receive logs -* credential source (one of the following): +* access data to journal indicated in the parameter ```journal-fqdn``` (one of the following): * password: - * ```journal-password``` – Credential (password) to journal indicated in the parameter ```journal-fqdn``` + * ```journal-password``` – Credential (password) of journal * service account: - * ```journal-sa-id``` – eg. ```/iam/sa/project/5e7b89edd93046f509ca38d7/sa/5e7b89edd93046f509ca38d7``` + * ```journal-sa-id``` – ID of service account eg. ```/iam/sa/project/5e7b89edd93046f509ca38d7/sa/5e7b89edd93046f509ca38d7``` * ```journal-sa-kid``` – ID of certificate of service account eg. ```5e7b89edd93046f509ca38d7``` * ```journal-private-key``` – private key of certificate added to actor * ```journal-credential-endpoint``` – credential endpoint eg. metadata service diff --git a/driver.js b/driver.js index 5a9b8e4..66e1565 100644 --- a/driver.js +++ b/driver.js @@ -90,7 +90,7 @@ module.exports = () => { await log.client.checkJournalToken(); } catch (err) { console.error(err); - throw new Error('Invalid/missing access data for journal'); + throw new Error('Invalid/missing access data for journal.'); } log.interval = setInterval(flushLogBuffer, flush_interval, log); diff --git a/journal.js b/journal.js index 18a8885..1af5a55 100644 --- a/journal.js +++ b/journal.js @@ -33,7 +33,7 @@ module.exports = (config) => { Authorization: `Bearer ${token}`, }; } - + Invalid/missing access data for journal. if (config['journal-credential-endpoint']) { if (!cred_req) { // no credential cred_req = agent.post(config['journal-credential-endpoint']) diff --git a/tests/e2e/e2e.bats b/tests/e2e/e2e.bats index 9780db2..3ee6495 100644 --- a/tests/e2e/e2e.bats +++ b/tests/e2e/e2e.bats @@ -70,7 +70,7 @@ teardown() { --log-opt labels=dockerbats \ --label dockerbats="$BATS_TEST_NAME" \ alpine id; - [[ $output =~ "Missing 'journal-fqdn option of log driver." ]] + [[ $output =~ "Missing 'journal-fqdn' option of log driver." ]] [ "$status" -eq 125 ] } @@ -81,6 +81,6 @@ teardown() { --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ --log-opt journal-password="invalid token" \ alpine id; - [[ $output =~ "Invalid journal-password." ]] + [[ $output =~ "Invalid/missing access data for journal." ]] [ "$status" -eq 125 ] } \ No newline at end of file From 1dbde4374c1a7485e5a62a9f1e0eea4e53b284a8 Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Wed, 25 Mar 2020 23:29:44 +0100 Subject: [PATCH 3/9] Fix tests --- journal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/journal.js b/journal.js index 1af5a55..18a8885 100644 --- a/journal.js +++ b/journal.js @@ -33,7 +33,7 @@ module.exports = (config) => { Authorization: `Bearer ${token}`, }; } - Invalid/missing access data for journal. + if (config['journal-credential-endpoint']) { if (!cred_req) { // no credential cred_req = agent.post(config['journal-credential-endpoint']) From 4f502fa7759056e1f8f698b1d715f36c80e3406d Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Wed, 25 Mar 2020 23:32:06 +0100 Subject: [PATCH 4/9] Ignore rootfs in eslint --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index eafe99a..68d0e08 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,6 @@ "@hyperone/eslint-config": "^2.0.1", "ava": "^3.5.1", "eslint": "^6.8.0" - } + }, + "eslintIgnore": ["rootfs"] } From 4f80ed34b0dd31a30ceeb3e93281fec7726a6e6e Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Mon, 30 Mar 2020 19:00:14 +0200 Subject: [PATCH 5/9] Update passport support --- README.md | 8 ++------ journal.js | 11 ++++++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bc1f40b..5d3432b 100644 --- a/README.md +++ b/README.md @@ -67,12 +67,8 @@ Each message has the following tags assigned by default. The user has the abilit * ```journal-fqdn``` – Journal FQDN that will receive logs * access data to journal indicated in the parameter ```journal-fqdn``` (one of the following): - * password: - * ```journal-password``` – Credential (password) of journal - * service account: - * ```journal-sa-id``` – ID of service account eg. ```/iam/sa/project/5e7b89edd93046f509ca38d7/sa/5e7b89edd93046f509ca38d7``` - * ```journal-sa-kid``` – ID of certificate of service account eg. ```5e7b89edd93046f509ca38d7``` - * ```journal-private-key``` – private key of certificate added to actor + * 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 diff --git a/journal.js b/journal.js index 18a8885..fc1fdab 100644 --- a/journal.js +++ b/journal.js @@ -19,14 +19,15 @@ module.exports = (config) => { return { 'x-auth-password': config['journal-password'] }; } - if (config['journal-sa-id'] && config['journal-sa-kid'] && config['journal-private-key']) { - const token = jwt.sign({}, config['journal-private-key'], { + if (config['journal-passport']) { + const passport = JSON.parse(config['journal-passport'].trim()); + const token = jwt.sign({}, passport.private_key, { algorithm: 'RS256', expiresIn: '5m', - keyid: config['journal-sa-kid'], + keyid: passport.certificate_id, audience: config['journal-fqdn'], - issuer: `${API_URL}${config['journal-sa-id']}`, - subject: config['journal-sa-id'], + issuer: passport.issuer, + subject: passport.subject_id, }); return { From 96fee0da8f440ed4c75fb1f53f43380251a1f42a Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Mon, 30 Mar 2020 19:02:37 +0200 Subject: [PATCH 6/9] Fix formatting --- journal.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/journal.js b/journal.js index fc1fdab..b9e44a6 100644 --- a/journal.js +++ b/journal.js @@ -5,8 +5,6 @@ const jwt = require('jsonwebtoken'); const WebSocket = require('ws'); const { FilterJournalDockerStream, ParseJournalStream } = require('./transform'); -const API_URL = 'https://api.hyperone.com/v2'; - module.exports = (config) => { const proto = config['journal-unsecure'] ? 'http' : 'https'; const url = `${proto}://${config['journal-fqdn']}/log`; From ab0a7c667ffb6f2ecf939b3d43f387f0aac9f08f Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Mon, 30 Mar 2020 19:21:10 +0200 Subject: [PATCH 7/9] Fix formatting of tests --- tests/e2e/e2e.bats | 48 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/e2e/e2e.bats b/tests/e2e/e2e.bats index 3ee6495..3c7b3cd 100644 --- a/tests/e2e/e2e.bats +++ b/tests/e2e/e2e.bats @@ -15,12 +15,12 @@ teardown() { @test "plugin send logs" { run docker run \ - --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ - --label dockerbats="$BATS_TEST_NAME" \ - --log-opt labels=dockerbats \ - --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ - --log-opt journal-password=${JOURNAL_TOKEN} \ - alpine sh -c 'echo $RANDOM'; + --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ + --label dockerbats="$BATS_TEST_NAME" \ + --log-opt labels=dockerbats \ + --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ + --log-opt journal-password=${JOURNAL_TOKEN} \ + alpine sh -c 'echo $RANDOM'; [ "$status" -eq 0 ]; containerId=$(docker container ls -a -q --filter label=dockerbats="$BATS_TEST_NAME"); run docker logs "${containerId}"; @@ -30,12 +30,12 @@ teardown() { @test "plugin flush logs" { run docker run -d \ - --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ - --label dockerbats="$BATS_TEST_NAME" \ - --log-opt labels=dockerbats \ - --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ - --log-opt journal-password=${JOURNAL_TOKEN} \ - alpine sh -c 'seq 1 10; sleep 30'; + --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ + --label dockerbats="$BATS_TEST_NAME" \ + --log-opt labels=dockerbats \ + --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ + --log-opt journal-password=${JOURNAL_TOKEN} \ + alpine sh -c 'seq 1 10; sleep 30'; [ "$status" -eq 0 ]; # Wait for flush (15 second default) sleep 20; @@ -48,12 +48,12 @@ teardown() { @test "plugin sends multiple lines of logs" { token=${RANDOM}; run docker run \ - --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ + --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ --label dockerbats="$BATS_TEST_NAME-${token}" \ --log-opt labels=dockerbats \ - --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ - --log-opt journal-password=${JOURNAL_TOKEN} \ - alpine sh -c "seq 100 | while read line; do echo \"multiple-\${line}-${token}\"; done;"; + --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ + --log-opt journal-password=${JOURNAL_TOKEN} \ + alpine sh -c "seq 100 | while read line; do echo \"multiple-\${line}-${token}\"; done;"; [ "$status" -eq 0 ] containerId=$(docker container ls -a -q --filter label=dockerbats="${BATS_TEST_NAME}-${token}"); echo "Container id: ${containerId}"; @@ -66,21 +66,21 @@ teardown() { @test "plugin require token" { run docker run -d \ - --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ + --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ --log-opt labels=dockerbats \ --label dockerbats="$BATS_TEST_NAME" \ - alpine id; - [[ $output =~ "Missing 'journal-fqdn' option of log driver." ]] + alpine id; + [[ $output =~ "Missing 'journal-fqdn' option of log driver" ]] [ "$status" -eq 125 ] } @test "plugin validate token" { run docker run -d \ - --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ + --log-driver 'h1cr.io/h1-docker-logging-plugin:latest' \ --label dockerbats="$BATS_TEST_NAME" \ - --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ - --log-opt journal-password="invalid token" \ - alpine id; - [[ $output =~ "Invalid/missing access data for journal." ]] + --log-opt journal-fqdn=${JOURNAL_ID}.journal.pl-waw-1.hyperone.cloud \ + --log-opt journal-password="invalid token" \ + alpine id; + [[ $output =~ "Invalid/missing access data for journal" ]] [ "$status" -eq 125 ] } \ No newline at end of file From ba27b4a858870639a6258045ce5e772f16142eed Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Tue, 31 Mar 2020 17:06:21 +0200 Subject: [PATCH 8/9] Stop use expires_in --- journal.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/journal.js b/journal.js index b9e44a6..8c1b7e9 100644 --- a/journal.js +++ b/journal.js @@ -5,12 +5,15 @@ const jwt = require('jsonwebtoken'); const WebSocket = require('ws'); const { FilterJournalDockerStream, ParseJournalStream } = require('./transform'); +const timeskew = 20; + module.exports = (config) => { 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']) { @@ -34,20 +37,30 @@ module.exports = (config) => { } 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'] }); + .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 cred_resp = await cred_req; - const ts = Math.round(new Date().getTime() / 1000); + const credential = await cred_req; // expired response - if (cred_resp.body.expires_on <= ts - 30) { + 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(); } - const token = cred_resp.body.access_token; + console.log(`Access token is fresh. Valid until ${exp}. Re-use.`); + + const token = credential.access_token; return { Authorization: `Bearer ${token}`, From b3c19797d93f29573a85357d4d299b158192a925 Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Wed, 1 Apr 2020 23:09:29 +0200 Subject: [PATCH 9/9] Update plugin to expose error in credential --- driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver.js b/driver.js index 66e1565..1c00b89 100644 --- a/driver.js +++ b/driver.js @@ -90,7 +90,7 @@ module.exports = () => { await log.client.checkJournalToken(); } catch (err) { console.error(err); - throw new Error('Invalid/missing access data for journal.'); + throw new Error(`Invalid/missing access data for journal: ${err}`); } log.interval = setInterval(flushLogBuffer, flush_interval, log);