From 8a9728e86d6346f559dae7cc556fba508dffc9c0 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 5 Feb 2024 11:17:12 +0000 Subject: [PATCH 1/5] add audit logging for node-red on device -> to platform --- lib/auditLogger/index.js | 37 +++++++++++++++++++++++++++++++ lib/launcher.js | 8 ++++++- lib/template/template-settings.js | 17 ++++++++++++++ package.json | 3 ++- 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 lib/auditLogger/index.js diff --git a/lib/auditLogger/index.js b/lib/auditLogger/index.js new file mode 100644 index 0000000..f9ad324 --- /dev/null +++ b/lib/auditLogger/index.js @@ -0,0 +1,37 @@ +/* + * The below code should be kept in-sync with nr-launcher/lib/auditLogger/index.js + */ + +const { default: got } = require('got') + +module.exports = (settings) => { + const loggingURL = settings.loggingURL + const token = settings.token + const logger = function (msg) { + if (/^(comms\.|.*\.get$)/.test(msg.event)) { + // Ignore comms events and any .get event that is just reading data + return + } + if (/^auth/.test(msg.event) && !/^auth.log/.test(msg.event)) { + return + } + if (msg.user) { + msg.user = msg.user.userId + } + delete msg.username + delete msg.level + got.post(loggingURL, { + json: msg, + responseType: 'json', + headers: { + 'user-agent': 'FlowFuse Device Agent Audit Logging v0.1', + authorization: 'Bearer ' + token + } + }).catch(err => { + // ignore errors for now + console.log(err) + }) + } + + return logger +} diff --git a/lib/launcher.js b/lib/launcher.js index fcdc503..93bcedf 100644 --- a/lib/launcher.js +++ b/lib/launcher.js @@ -28,7 +28,7 @@ class Launcher { this.restartCount = 0 this.startTime = [] this.state = 'stopped' - + this.auditLogURL = `${this.config.forgeURL}/logging/device/${this.config.deviceId}/audit` this.projectDir = path.join(this.config.dir, 'project') this.files = { @@ -176,6 +176,12 @@ class Launcher { projectID: this.project || undefined, applicationID: this.application || undefined, teamID, + deviceId: this.config.deviceId, + auditLogger: { + url: this.auditLogURL, + token: this.config.token, + bin: path.join(__dirname, 'auditLogger', 'index.js') + }, projectLink } } diff --git a/lib/template/template-settings.js b/lib/template/template-settings.js index 45d03de..eeb2212 100644 --- a/lib/template/template-settings.js +++ b/lib/template/template-settings.js @@ -119,6 +119,23 @@ const runtimeSettings = { editorTheme: { ...editorTheme } } +if (settings.flowforge.auditLogger?.bin && settings.flowforge.auditLogger?.url) { + try { + runtimeSettings.logging.auditLogger = { + level: 'off', + audit: true, + handler: require(settings.flowforge.auditLogger.bin), + baseURL: settings.flowforge.baseURL, + loggingURL: settings.flowforge.auditLogger.url, + projectID: settings.flowforge.projectID, + deviceID: settings.flowforge.deviceID, + token: settings.flowforge.auditLogger.token + } + } catch (e) { + console.warn('Could not initialise device audit logging. Audit events will not be logged to the platform') + } +} + if (settings.https) { ;['key', 'ca', 'cert'].forEach(key => { const filePath = settings.https[`${key}Path`] diff --git a/package.json b/package.json index cdedffc..cf182c5 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "version": "2.0.0", "description": "An Edge Agent for running Node-RED instances deployed from the FlowFuse Platform", "exports": { - "./libraryPlugin": "./lib/plugins/libraryPlugin.js" + "./libraryPlugin": "./lib/plugins/libraryPlugin.js", + "./auditLogger": "./lib/auditLogger/index.js" }, "main": "index.js", "repository": { From 838101274b3d0f7df8a99390047c981c6aa7bf80 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 5 Feb 2024 15:12:07 +0000 Subject: [PATCH 2/5] ensure settings file gets auditLogger info --- test/unit/lib/launcher_spec.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/unit/lib/launcher_spec.js b/test/unit/lib/launcher_spec.js index 146fea6..3672050 100644 --- a/test/unit/lib/launcher_spec.js +++ b/test/unit/lib/launcher_spec.js @@ -16,8 +16,16 @@ describe('Launcher', function () { verbose: true } + const configWithPlatformInfo = { + ...config, + forgeURL: 'https://test', + token: 'test-token', + deviceId: 'deviceid' + } + beforeEach(async function () { config.dir = await fs.mkdtemp(path.join(os.tmpdir(), 'ff-launcher-')) + configWithPlatformInfo.dir = config.dir await fs.mkdir(path.join(config.dir, 'project')) }) @@ -157,4 +165,18 @@ describe('Launcher', function () { settings.editorTheme.should.have.property('palette') settings.editorTheme.palette.should.not.have.a.property('catalogue') }) + it('sets up audit logging for the node-red instance', async function () { + const launcher = newLauncher(configWithPlatformInfo, null, 'projectId', setup.snapshot) + const expectedURL = `${configWithPlatformInfo.forgeURL}/logging/device/${configWithPlatformInfo.deviceId}/audit` + should(launcher).be.an.Object() + launcher.should.have.property('auditLogURL', expectedURL) + await launcher.writeSettings() + const setFile = await fs.readFile(path.join(config.dir, 'project', 'settings.json')) + const settings = JSON.parse(setFile) + settings.should.have.property('flowforge') + settings.flowforge.should.have.property('auditLogger').and.be.an.Object() + settings.flowforge.auditLogger.should.have.property('url', expectedURL) + settings.flowforge.auditLogger.should.have.property('token', configWithPlatformInfo.token) + settings.flowforge.auditLogger.should.have.property('bin', path.join(__dirname, '..', '..', '..', 'lib', 'auditLogger', 'index.js')) + }) }) From f75cae0cb739744cb1a39087b4e1ecfc1768a7cb Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 5 Feb 2024 15:40:12 +0000 Subject: [PATCH 3/5] remove unnecessary props in the auditLogger object --- lib/template/template-settings.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/template/template-settings.js b/lib/template/template-settings.js index eeb2212..995ce61 100644 --- a/lib/template/template-settings.js +++ b/lib/template/template-settings.js @@ -125,10 +125,7 @@ if (settings.flowforge.auditLogger?.bin && settings.flowforge.auditLogger?.url) level: 'off', audit: true, handler: require(settings.flowforge.auditLogger.bin), - baseURL: settings.flowforge.baseURL, loggingURL: settings.flowforge.auditLogger.url, - projectID: settings.flowforge.projectID, - deviceID: settings.flowforge.deviceID, token: settings.flowforge.auditLogger.token } } catch (e) { From a412beea0873354074de88412535aec907958619 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 5 Feb 2024 15:41:10 +0000 Subject: [PATCH 4/5] ensure required prop deviceId is included --- test/unit/lib/launcher_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/lib/launcher_spec.js b/test/unit/lib/launcher_spec.js index 3672050..de5dc33 100644 --- a/test/unit/lib/launcher_spec.js +++ b/test/unit/lib/launcher_spec.js @@ -174,6 +174,7 @@ describe('Launcher', function () { const setFile = await fs.readFile(path.join(config.dir, 'project', 'settings.json')) const settings = JSON.parse(setFile) settings.should.have.property('flowforge') + settings.flowforge.should.have.property('deviceId', configWithPlatformInfo.deviceId) settings.flowforge.should.have.property('auditLogger').and.be.an.Object() settings.flowforge.auditLogger.should.have.property('url', expectedURL) settings.flowforge.auditLogger.should.have.property('token', configWithPlatformInfo.token) From a5da06b7b325bbe272f96c60b1eaf55ba3769093 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Tue, 6 Feb 2024 10:10:10 +0000 Subject: [PATCH 5/5] additional test to ensure settings.js loads the auditLogger --- test/unit/lib/launcher_spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/unit/lib/launcher_spec.js b/test/unit/lib/launcher_spec.js index de5dc33..62c7254 100644 --- a/test/unit/lib/launcher_spec.js +++ b/test/unit/lib/launcher_spec.js @@ -180,4 +180,21 @@ describe('Launcher', function () { settings.flowforge.auditLogger.should.have.property('token', configWithPlatformInfo.token) settings.flowforge.auditLogger.should.have.property('bin', path.join(__dirname, '..', '..', '..', 'lib', 'auditLogger', 'index.js')) }) + it('settings.js loads audit logger with settings from config', async function () { + const launcher = newLauncher(configWithPlatformInfo, null, 'projectId', setup.snapshot) + const expectedURL = `${configWithPlatformInfo.forgeURL}/logging/device/${configWithPlatformInfo.deviceId}/audit` + await launcher.writeSettings() + + // copy the template-settings as settings.js to the test dir + await fs.copyFile(path.join(__dirname, '..', '..', '..', 'lib', 'template', 'template-settings.js'), path.join(config.dir, 'project', 'settings.js')) + const runtimeSettings = require(path.join(config.dir, 'project', 'settings.js')) + should(runtimeSettings).be.an.Object() + runtimeSettings.should.have.property('logging').and.be.an.Object() + runtimeSettings.logging.should.have.property('auditLogger').and.be.an.Object() + runtimeSettings.logging.auditLogger.should.have.property('level', 'off') + runtimeSettings.logging.auditLogger.should.have.property('audit', true) + runtimeSettings.logging.auditLogger.should.have.property('handler').and.be.a.Function() + runtimeSettings.logging.auditLogger.should.have.property('loggingURL', expectedURL) + runtimeSettings.logging.auditLogger.should.have.property('token', configWithPlatformInfo.token) + }) })