first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,170 @@
# Syslog Manual Testing
Please note: You will need an enterprise licence in n8n to configure this.
## With TCP
### Step 1 - Configure and start syslog-ng
Create a new directory to contain your syslog config (doesn't have to be the home dir):
```sh
mkdir -p ~/syslog-tcp-test && cd ~/syslog-tcp-test
```
Create a file `syslog-ng.conf` and paste the following content:
```
@version: 4.10
@include "scl.conf"
source s_tcp {
network(
transport("tcp")
port(514)
flags(no-parse)
);
};
destination d_console {
file("/proc/1/fd/1" template("RECEIVED: $MSG\n"));
};
log {
source(s_tcp);
destination(d_console);
flags(flow-control);
};
```
Start a local instance with docker and validate it is working
```shell
docker run -d \
--name syslog-ng-tcp \
-p 514:514 \
-v $(pwd)/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf:ro \
balabit/syslog-ng:latest
# In one window
docker logs -f syslog-ng-tcp
# In another window
echo "test message" | nc -v localhost 514
# You should see the message in the window tailing the logs
```
### Step 2 - Configure log streaming in n8n
Head to n8n log streaming settings and enter the following:
```
Host: localhost
Port: 514
Protocol: TCP
Facility: Local0
App Name: n8n
```
Once saved you can send a test message and validate it is received using the `docker logs syslog-ng-tcp` command.
## With TLS
These instructions will help you setup a syslog server that will accept TLS connections. They will be useful if you need
to manually validate anything specific around TLS & syslog configuration.
### Step 1 - Setup Test Certs
```shell
mkdir -p ~/syslog-tls-test && cd ~/syslog-tls-test
# CA certificate
openssl req -x509 -newkey rsa:4096 \
-keyout ca-key.pem \
-out ca-cert.pem \
-days 365 -nodes \
-subj "/CN=Test CA/O=Test Org"
# Server key
openssl genrsa -out server-key.pem 4096
# Server certificate signing request
openssl req -new \
-key server-key.pem \
-out server-csr.pem \
-subj "/CN=localhost/O=Test Server"
# Sign server certificate using CA
openssl x509 -req \
-in server-csr.pem \
-CA ca-cert.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out server-cert.pem \
-days 365 \
-sha256
```
### Step 2 - Configure and start syslog-ng
Create a file `syslog-ng.conf` and paste the following content:
```
@version: 4.10
@include "scl.conf"
source s_tls {
network(
transport("tls")
port(6514)
flags(no-parse)
tls(
key-file("/certs/server-key.pem")
cert-file("/certs/server-cert.pem")
peer-verify(optional-untrusted)
)
);
};
destination d_console {
file("/proc/1/fd/1" template("RECEIVED: $MSG\n"));
};
log {
source(s_tls);
destination(d_console);
flags(flow-control);
};
```
Start a local instance with docker and validate it is working
```shell
docker run -d \
--name syslog-ng-tls \
-p 6514:6514 \
-v $(pwd)/ca-cert.pem:/certs/ca-cert.pem:ro \
-v $(pwd)/server-cert.pem:/certs/server-cert.pem:ro \
-v $(pwd)/server-key.pem:/certs/server-key.pem:ro \
-v $(pwd)/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf:ro \
balabit/syslog-ng:latest
# In one window
docker logs -f syslog-ng-tls
# In another window
echo 'TEST MESSAGE' | \
openssl s_client -connect localhost:6514 -CAfile ca-cert.pem -ign_eof 2>&1
# You should see the message in the window tailing the logs
```
### Step 3 - Configure log streaming in n8n
Head to n8n log streaming settings and enter the following:
```
Host: localhost // This is important as the certificate CN=localhost
Port: 6514
Protocol: TLS
TlsCa: Paste the contents of ca-cert.pem created in step 1
Facility: Local0
App Name: n8n
```
Once saved you can send a test message and validate it is received using the `docker logs syslog-ng-tls` command.
Most problems result in a log in the n8n system - error feedback will hopefully be improved in
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDfzCCAmegAwIBAgIUEo4pU1hgsKHlV01QIN3hhDaQ6ggwDQYJKoZIhvcNAQEL
BQAwTjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3Qx
DTALBgNVBAoMBFRlc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yNjAxMDUyMDQ1
NDhaGA8yMDUzMDUyMzIwNDU0OFowTjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRl
c3QxDTALBgNVBAcMBFRlc3QxDTALBgNVBAoMBFRlc3QxEjAQBgNVBAMMCWxvY2Fs
aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzoP33jcgWYJ/mS
A8NQm9gABgeOVHlc4lEqHsMo9gZy3GfmanI/XFpTr3Nxeb2AMqJ1B9EgXOtJLBDP
J+Z+k3jN9Sv2ZRSClf6ZZUUPFKcjQ6TNYXG/Jgmjoku7k0I9cJluXE8VwlC9mzty
iHbZGo/tRtU4LkbaedmL2eTctcBukX4g99uVMJVKot5l8PWxZ1seVZ6T4IiRcaRo
NcQqL67xspm3KmlrVx/WzlM41BfGj2k+L4Bgenm/bIg1RPQ+MoETViVFGeKgKjkp
kTOjCsZqeK2STCIj1+XI4tLMCxV0B95fw7bcFfEtbnL9XyEjyn+3A33625EJMoiq
OX6bb9MCAwEAAaNTMFEwHQYDVR0OBBYEFEB7f6J6nW90lXS8i4gOR/6rya7qMB8G
A1UdIwQYMBaAFEB7f6J6nW90lXS8i4gOR/6rya7qMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQELBQADggEBAIXoFrV9saNREtlDkMp5O66IO+UFhr1NcLYmXvhs
n3H+OFpg/Uo+/p++vHgf5UKNTdx24pO/88FyPujIcq1mfemrAIgcd8t++aclzSlf
G49bZvhYyxsE+qoyl7XWtmYLPydCpYKkL7krLwUVzSeC/f6f/GWEn0vvwWPht5ky
dR/Uze7pg/Ux12gztSLPV77n9gBua6lj5HKhXYGoIFuZSw9n1tWqndT28OQ3VwI7
ZSpFFJmDyevspC2F18NaTE5VmXv9gckeUmqx8nr0zOmWWREVnFt+d4NNvrNLEChj
IJFfkzynUHYTe+1oQkv8Evr0GHU5ocHsqOQ4NqW5gx0XXRA=
-----END CERTIFICATE-----
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIUfC52wMYMzcoL9c/GY3Ks0beSH2kwDQYJKoZIhvcNAQEL
BQAwTTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVdyb25nMQ4wDAYDVQQHDAVXcm9u
ZzEOMAwGA1UECgwFV3JvbmcxDjAMBgNVBAMMBXdyb25nMCAXDTI2MDEwNTIwNDU0
OFoYDzIwNTMwNTIzMjA0NTQ4WjBNMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFV3Jv
bmcxDjAMBgNVBAcMBVdyb25nMQ4wDAYDVQQKDAVXcm9uZzEOMAwGA1UEAwwFd3Jv
bmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCj1725lupt3pTvMf2O
o931s2l4ObA5qTC6ihlWJX+A7R4RkHrQTlwahEwIgMNSzNURtXOwu3ePyqZE3Luk
pVWGMFdgedNuU+4zNUXXamIyIJfsMuEmw6xrWLpdWqawo0G50Y2RxB4MN6/127PL
EqogXC+0PHdgBUWtso/sJcGRrdQvGpmH9ZXavVNhER4G3q6f/Tcp1eu0xry1UwGV
WN/DghkE5R/9mLm830Lr7gLpEQ6JKUUTnPKaZQW9m9G8gjr6oEXR9QaTcTariHgx
HT0wkGRKO54CC3RdBxqwucdkvgUI7OkaGl8tS46QMI4DwQHcUFLkqx29wXHY/Zhu
mHxDAgMBAAGjUzBRMB0GA1UdDgQWBBT8E+hqfJTcai2AlqT27R4KL/nGNTAfBgNV
HSMEGDAWgBT8E+hqfJTcai2AlqT27R4KL/nGNTAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBCwUAA4IBAQCf38uZYe6+cW7/juw2wCoLObSBoomRuJicwpRbDJpo
kzKzlBm4KbAwrz0+2kugZCrqrY1pLZPozQiqqf7UOImNcBi/wCbNk6iksdD9WRpv
tooKb2UHY9Z+1VJ7FqBhEekUJnxAoOlmOnSwPW3qDQNaP+niWZEZjgK+m+3/ZI5d
oW8lU1UGYJs/pZrZDwPUGHRnPj7Sa6q0S5OCVFx4BN+YMKN9ynvVG0z0pkO3QpA3
wOYu/WxINCuFb6Iu469Ov/FoSfnqZRcqZ+PlMDWHr0NCj0DNBP/eGq8VWy6VRabQ
SyRRjByPFe8fK1PAbrwIh8w6QIWz28OQZjAz0TsHnK7D
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDM6D9943IFmCf5
kgPDUJvYAAYHjlR5XOJRKh7DKPYGctxn5mpyP1xaU69zcXm9gDKidQfRIFzrSSwQ
zyfmfpN4zfUr9mUUgpX+mWVFDxSnI0OkzWFxvyYJo6JLu5NCPXCZblxPFcJQvZs7
coh22RqP7UbVOC5G2nnZi9nk3LXAbpF+IPfblTCVSqLeZfD1sWdbHlWek+CIkXGk
aDXEKi+u8bKZtyppa1cf1s5TONQXxo9pPi+AYHp5v2yINUT0PjKBE1YlRRnioCo5
KZEzowrGanitkkwiI9flyOLSzAsVdAfeX8O23BXxLW5y/V8hI8p/twN9+tuRCTKI
qjl+m2/TAgMBAAECggEARqkwe/EbOOKnpmC7+3aox2v6qFBkwRVO1j54LUTK0p55
czr6Kju/VUbWkjDnknnK/ErvTyah5GFvWLyXPd0YbehYS7jEUrZVk17ClXYF4T0/
7m0E1XzdWSWcqhEdTxJw6fgszPjr8XvxNCbi+FkV19wzOQQOsVBWBLc9hLa31ous
CCMmpmJD+KaaN1D7VKDyLOtMQDPM7xft9VcWTU0qATN86HzvTxRF3+wWi1Z8ChV+
miMfu4yb4Ip/tFSZfCJQqWupxdXYxw+p6HbcYRxSFa8ux4oZoQwvSVIi3pGKFXij
/0oBhx2N1Y0T5LnuUZvne6CjwSyRSCmN68FnUKiogQKBgQD5UuHXcMZiDyZSskpj
6rj//Hc3I9eRvB2CIvxVbDecRoCat3r9SZmZJ+DFxyH9Voy0ClrQnjSjPsXR5T89
Ya+dFELl0zg7scHMw9VVSBsIjPrBhCnqPII1tfZj5ZzKI0oM1P8DwsoBOMotZIjl
eHsUsd3Qw5sogbrwJ5d0cuDcaQKBgQDSZOPLwuEtAc7qyu1lSbifCMYwM4ISac3F
2f116BV43/9L8eQv41XmVl1ELmwebahQdIbTtP5bNGDRHLRyIfbsSWxzBamQkKX9
INzsiaeCENMTEHo4oSXX89DoZkr50iT62y9DKF5TkzNkA+CMvUuKfkh8YZAnLizA
dhrM0NOS2wKBgQD3nJ/ctgzgEEmwMY994fxvhw3i/j62ZswrlZFjSQGFu+M3ROfp
q3HXUGqEIbuY/Z7po6sDq0t9oPcX+QQwctbVOu0hkP81Edn2AbvaYa0vdcwH6rkx
/3wV2axlFAH+IsQFMHhABPFX+02XfVQUCe6649b24X0z4nuEzN028mxtmQKBgC7q
XHmVbUzx+EgXFDTf6ZFdDYS2F60vdrlF0OU36YHYwT8YI4AiO4dvfsCzcVyfq0a1
lVMkKwv/dA9tTTeeJ0etX8eOXa9k8f6uE1WTpIy2X7sCk56JIL01G0KXfOSUXbaq
pbFeERql2nHVA3+evneVjWhfARwbidMpAvTlJCi9AoGAQxFKi/dKzjZQFkikv9xU
QpvtrfjZvYHU56HMDkKA4yIjBpONPWc6TAir5eOroKIKxAF0K+5s/bcQqZagShfZ
WhZSdUid4J+gPCYL2v5F8symu9dxP54xRw1886uJUuqyb7tHhdFCOHRclD5wlLg3
OrfEUwhYRn7d21UZoGDAvYU=
-----END PRIVATE KEY-----
@@ -0,0 +1,136 @@
import { Logger } from '@n8n/backend-common';
import { GLOBAL_OWNER_ROLE } from '@n8n/db';
import { Container } from '@n8n/di';
import * as fs from 'fs';
import * as path from 'path';
import type TestAgent from 'supertest/lib/agent';
import { EventMessageGeneric } from '@/eventbus/event-message-classes/event-message-generic';
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
import { LogStreamingDestinationService } from '@/modules/log-streaming.ee/log-streaming-destination.service';
import { TlsSyslogServer } from './tls-server';
import { createUser } from '../shared/db/users';
import * as utils from '../shared/utils';
jest.unmock('@/eventbus/message-event-bus/message-event-bus');
const tlsServer = new TlsSyslogServer();
let serverPort: number;
let eventBus: MessageEventBus;
let destinationService: LogStreamingDestinationService;
let authOwnerAgent: TestAgent;
let logger: Logger;
const testServer = utils.setupTestServer({
endpointGroups: ['eventBus'],
enabledFeatures: ['feat:logStreaming'],
modules: ['log-streaming'],
});
afterAll(async () => {
await eventBus?.close();
// Undo the "unmock" from above
jest.mock('@/eventbus/message-event-bus/message-event-bus');
await tlsServer.stop();
});
beforeAll(async () => {
serverPort = await tlsServer.start();
const owner = await createUser({ role: GLOBAL_OWNER_ROLE });
authOwnerAgent = testServer.authAgentFor(owner);
eventBus = Container.get(MessageEventBus);
logger = Container.get(Logger);
await eventBus.initialize();
destinationService = Container.get(LogStreamingDestinationService);
await destinationService.initialize();
});
describe('TLS Syslog E2E', () => {
const destinationId = 'e2e-tls-test';
const subscribedEvent = 'n8n.workflow.failed';
beforeEach(() => {
tlsServer.clearMessages();
});
afterEach(async () => {
const destinations = await destinationService.findDestination();
for (const destination of destinations) {
if (destination.id) {
await destinationService.removeDestination(destination.id, false);
}
}
});
test('should send message over real TLS connection', async () => {
const certificate = fs.readFileSync(path.join(__dirname, 'support', 'certificate.pem'), 'utf8');
await authOwnerAgent.post('/eventbus/destination').send({
__type: '$$MessageEventBusDestinationSyslog',
id: destinationId,
protocol: 'tls',
host: 'localhost',
port: serverPort,
label: 'E2E TLS Syslog',
enabled: true,
subscribedEvents: [subscribedEvent],
tlsCa: certificate,
});
const testMessage = new EventMessageGeneric({
eventName: subscribedEvent,
id: 'test-message-1',
});
await eventBus.send(testMessage);
// Wait for message to arrive at real server
const receivedMessage = await tlsServer.waitForMessage(5000);
expect(receivedMessage).toBeTruthy();
expect(receivedMessage).toContain('n8n.workflow.failed');
expect(receivedMessage).toContain('test-message-1');
});
test('should log an error when the certificate is invalid - but not break the application', async () => {
const loggerErrorSpy = jest.spyOn(logger, 'error');
const incorrectCertificate = fs.readFileSync(
path.join(__dirname, 'support', 'incorrect-certificate.pem'),
'utf8',
);
const authOwnerAgent = testServer.authAgentFor(await createUser({ role: GLOBAL_OWNER_ROLE }));
await authOwnerAgent.post('/eventbus/destination').send({
__type: '$$MessageEventBusDestinationSyslog',
id: destinationId,
protocol: 'tls',
host: 'localhost',
port: serverPort,
label: 'Invalid TLS',
enabled: true,
subscribedEvents: [subscribedEvent],
tlsCa: incorrectCertificate,
});
const testMessage = new EventMessageGeneric({
eventName: subscribedEvent,
id: 'test-invalid',
});
await eventBus.send(testMessage);
// Should NOT receive message (cert validation failed)
await expect(tlsServer.waitForMessage(2000)).rejects.toThrow('Timeout');
// Should output an error log message.
expect(loggerErrorSpy).toHaveBeenCalledWith('Transport error');
loggerErrorSpy.mockRestore();
});
});
@@ -0,0 +1,58 @@
import * as tls from 'tls';
import * as fs from 'fs';
import * as path from 'path';
import * as readline from 'readline';
export class TlsSyslogServer {
private server: tls.Server | null = null;
private messages: string[] = [];
port: number = 0;
async start(): Promise<number> {
const key = fs.readFileSync(path.join(__dirname, 'support', 'key.pem'), 'utf8');
const cert = fs.readFileSync(path.join(__dirname, 'support', 'certificate.pem'), 'utf8');
return await new Promise((resolve) => {
this.server = tls.createServer({ key, cert, secureProtocol: 'TLSv1_2_method' }, (socket) => {
const lines = readline.createInterface({ input: socket });
lines.on('line', (line) => {
this.messages.push(line);
console.log('Received:', line);
});
});
this.server.listen(0, () => {
const address = this.server!.address();
this.port = address && typeof address === 'object' ? address.port : 0;
console.log(`TLS Syslog server listening on port ${this.port}`);
resolve(this.port);
});
});
}
async stop(): Promise<void> {
return await new Promise((resolve) => {
this.server?.close();
resolve();
});
}
getMessages(): string[] {
return [...this.messages];
}
clearMessages(): void {
this.messages = [];
}
async waitForMessage(timeout = 5000): Promise<string> {
const start = Date.now();
while (Date.now() - start < timeout) {
if (this.messages.length > 0) {
return this.messages.shift()!;
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
throw new Error('Timeout waiting for message');
}
}