Skip to content

Commit fd44a2a

Browse files
authored
fix: improvements to allow to use Bun and tls (#2119)
* uplift startTls code to be compatible with current bun * more tls refactoring * lint * ci: enable tls in bun matrix * fix ssl tests * lint * bun: only run 2 basic tests for now * lint * don't enable ssl in test running against fake server * fix typo * debug failures * try bun canary * ci: add osx runner * ci: install docker for osx * ci: install docker for osx * ci: install docker for osx * ci: osx - don't mount config in docker * handle ECONNREFUSED in waitDatabaseReady helper * comment out instead of early return * explicitly install lima * use connection.end() instead of destroy * debug Ssl_cipher assertion * more flexible Ssl_cipher assertion * initialize packet header befor writing * cleanup * add compression to bun matrix * only use bun v0.6.13 for non-ssl tests
1 parent 4ac6a8f commit fd44a2a

File tree

12 files changed

+171
-107
lines changed

12 files changed

+171
-107
lines changed

.github/workflows/ci-bun.yml

+24-7
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,19 @@ jobs:
2020
strategy:
2121
fail-fast: false
2222
matrix:
23-
bun-version: [0.5.1]
23+
bun-version: [canary]
2424
mysql-version: ["mysql:5.7", "mysql:8.0.18", "mysql:8.0.22"]
25-
use-compression: [0]
26-
use-tls: [0]
25+
use-compression: [0, 1]
26+
use-tls: [0,1]
27+
include:
28+
- bun-version: "0.6.13"
29+
use-compression: 1
30+
use-tls: 0
31+
mysql-version: "mysql:8.0.18"
32+
- bun-version: "0.6.13"
33+
use-compression: 1
34+
use-tls: 0
35+
mysql-version: "mysql:8.0.22"
2736

2837
name: Bun ${{ matrix.bun-version }} - DB ${{ matrix.mysql-version }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}}
2938

@@ -33,7 +42,7 @@ jobs:
3342
run: docker run -d -e MYSQL_ALLOW_EMPTY_PASSWORD=1 -e MYSQL_DATABASE=${{ env.MYSQL_DATABASE }} -v $PWD/mysqldata:/var/lib/mysql/ -v $PWD/examples/custom-conf:/etc/mysql/conf.d -v $PWD/examples/ssl/certs:/certs -p ${{ env.MYSQL_PORT }}:3306 ${{ matrix.mysql-version }}
3443

3544
- name: Set up Bun ${{ matrix.bun-version }}
36-
uses: oven-sh/setup-bun@v0.1.8
45+
uses: oven-sh/setup-bun@v1
3746
with:
3847
bun-version: ${{ matrix.bun-version }}
3948

@@ -57,6 +66,14 @@ jobs:
5766
- name: Wait mysql server is ready
5867
run: node tools/wait-up.js
5968

60-
- name: Run tests
61-
# todo: run full test suite once test createServer is implemented using Bun.listen
62-
run: FILTER=test-select MYSQL_PORT=3306 bun run test
69+
# todo: run full test suite once test createServer is implemented using Bun.listen
70+
- name: run tests
71+
env:
72+
MYSQL_USER: ${{ env.MYSQL_USER }}
73+
MYSQL_DATABASE: ${{ env.MYSQL_DATABASE }}
74+
MYSQL_PORT: ${{ env.MYSQL_PORT }}
75+
MYSQL_USE_COMPRESSION: ${{ matrix.use-compression }}
76+
MYSQL_USE_TLS: ${{ matrix.use-tls }}
77+
run: |
78+
bun test/integration/connection/test-select-1.js
79+
bun test/integration/connection/test-select-ssl.js

.github/workflows/ci-osx.yml

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: CI - OSX
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [ main ]
7+
8+
workflow_dispatch:
9+
10+
env:
11+
MYSQL_PORT: 3306
12+
MYSQL_USER: root
13+
MYSQL_DATABASE: test
14+
15+
jobs:
16+
tests-osx:
17+
runs-on: macos-13
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
node-version: [18.x, 20.x]
22+
mysql-version: ["mysql:8.0.22", "mysql:8.0.33"]
23+
use-compression: [0, 1]
24+
use-tls: [0]
25+
mysql_connection_url_key: [""]
26+
# TODO - add mariadb to the matrix. currently few tests are broken due to mariadb incompatibilities
27+
include:
28+
# 20.x
29+
- node-version: "20.x"
30+
mysql-version: "mysql:8.0.33"
31+
use-compression: 1
32+
use-tls: 0
33+
use-builtin-test-runner: 1
34+
- node-version: "20.x"
35+
mysql-version: "mysql:8.0.33"
36+
use-compression: 0
37+
use-tls: 1
38+
use-builtin-test-runner: 1
39+
env:
40+
MYSQL_CONNECTION_URL: ${{ secrets[matrix.mysql_connection_url_key] }}
41+
42+
name: Node.js ${{ matrix.node-version }} - DB ${{ matrix.mysql-version }}${{ matrix.mysql_connection_url_key }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}}
43+
44+
steps:
45+
- uses: actions/checkout@v3
46+
47+
- name: install lima
48+
run: brew install lima
49+
50+
- name: Setup Docker on macOS
51+
uses: douglascamata/setup-docker-macos-action@v1-alpha
52+
53+
- name: Set up MySQL
54+
if: ${{ matrix.mysql-version }}
55+
run: docker run -d -e MYSQL_ALLOW_EMPTY_PASSWORD=1 -e MYSQL_DATABASE=${{ env.MYSQL_DATABASE }} -p ${{ env.MYSQL_PORT }}:3306 ${{ matrix.mysql-version }}
56+
57+
- name: Set up Node.js ${{ matrix.node-version }}
58+
uses: actions/setup-node@v3
59+
with:
60+
node-version: ${{ matrix.node-version }}
61+
62+
- name: Cache dependencies
63+
uses: actions/cache@v3
64+
with:
65+
path: ~/.npm
66+
key: npm-${{ hashFiles('package-lock.json') }}
67+
restore-keys: npm-
68+
69+
- name: Install npm dependencies
70+
run: npm ci
71+
72+
- name: Wait mysql server is ready
73+
if: ${{ matrix.mysql-version }}
74+
run: node tools/wait-up.js
75+
76+
- name: Run tests
77+
run: FILTER=${{matrix.filter}} MYSQL_USE_TLS=${{ matrix.use-tls }} MYSQL_USE_COMPRESSION=${{ matrix.use-compression }} npm run coverage-test
78+
79+
- name: Run tests with built-in node test runner
80+
if: ${{ matrix.use-builtin-test-runner }}
81+
run: FILTER=${{matrix.filter}} MYSQL_USE_TLS=${{ matrix.use-tls }} MYSQL_USE_COMPRESSION=${{ matrix.use-compression }} npm run test:builtin-node-runner

lib/connection.js

+24-87
Original file line numberDiff line numberDiff line change
@@ -355,62 +355,43 @@ class Connection extends EventEmitter {
355355
});
356356
const rejectUnauthorized = this.config.ssl.rejectUnauthorized;
357357
const verifyIdentity = this.config.ssl.verifyIdentity;
358-
const host = this.config.host;
358+
const servername = this.config.host;
359359

360360
let secureEstablished = false;
361-
const secureSocket = new Tls.TLSSocket(this.stream, {
362-
rejectUnauthorized: rejectUnauthorized,
363-
requestCert: true,
364-
secureContext: secureContext,
365-
isServer: false
361+
this.stream.removeAllListeners('data');
362+
const secureSocket = Tls.connect({
363+
rejectUnauthorized,
364+
requestCert: rejectUnauthorized,
365+
secureContext,
366+
isServer: false,
367+
socket: this.stream,
368+
servername
369+
}, () => {
370+
secureEstablished = true;
371+
if (rejectUnauthorized) {
372+
if (typeof servername === 'string' && verifyIdentity) {
373+
const cert = secureSocket.getPeerCertificate(true);
374+
const serverIdentityCheckError = Tls.checkServerIdentity(servername, cert);
375+
if (serverIdentityCheckError) {
376+
onSecure(serverIdentityCheckError);
377+
return;
378+
}
379+
}
380+
}
381+
onSecure();
366382
});
367-
if (typeof host === 'string') {
368-
secureSocket.setServername(host);
369-
}
370383
// error handler for secure socket
371-
secureSocket.on('_tlsError', err => {
384+
secureSocket.on('error', err => {
372385
if (secureEstablished) {
373386
this._handleNetworkError(err);
374387
} else {
375388
onSecure(err);
376389
}
377390
});
378-
secureSocket.on('secure', () => {
379-
secureEstablished = true;
380-
let callbackValue = null;
381-
if (rejectUnauthorized) {
382-
callbackValue = secureSocket.ssl.verifyError()
383-
if (!callbackValue && typeof host === 'string' && verifyIdentity) {
384-
const cert = secureSocket.ssl.getPeerCertificate(true);
385-
callbackValue = Tls.checkServerIdentity(host, cert)
386-
}
387-
}
388-
onSecure(callbackValue);
389-
});
390391
secureSocket.on('data', data => {
391392
this.packetParser.execute(data);
392393
});
393-
this.write = buffer => {
394-
secureSocket.write(buffer);
395-
};
396-
// start TLS communications
397-
secureSocket._start();
398-
}
399-
400-
pipe() {
401-
if (this.stream instanceof Net.Stream) {
402-
this.stream.ondata = (data, start, end) => {
403-
this.packetParser.execute(data, start, end);
404-
};
405-
} else {
406-
this.stream.on('data', data => {
407-
this.packetParser.execute(
408-
data.parent,
409-
data.offset,
410-
data.offset + data.length
411-
);
412-
});
413-
}
394+
this.write = buffer => secureSocket.write(buffer);
414395
}
415396

416397
protocolError(message, code) {
@@ -948,48 +929,4 @@ class Connection extends EventEmitter {
948929
}
949930
}
950931

951-
if (Tls.TLSSocket) {
952-
// not supported
953-
} else {
954-
Connection.prototype.startTLS = function _startTLS(onSecure) {
955-
if (this.config.debug) {
956-
// eslint-disable-next-line no-console
957-
console.log('Upgrading connection to TLS');
958-
}
959-
const crypto = require('crypto');
960-
const config = this.config;
961-
const stream = this.stream;
962-
const rejectUnauthorized = this.config.ssl.rejectUnauthorized;
963-
const credentials = crypto.createCredentials({
964-
key: config.ssl.key,
965-
cert: config.ssl.cert,
966-
passphrase: config.ssl.passphrase,
967-
ca: config.ssl.ca,
968-
ciphers: config.ssl.ciphers
969-
});
970-
const securePair = Tls.createSecurePair(
971-
credentials,
972-
false,
973-
true,
974-
rejectUnauthorized
975-
);
976-
977-
if (stream.ondata) {
978-
stream.ondata = null;
979-
}
980-
stream.removeAllListeners('data');
981-
stream.pipe(securePair.encrypted);
982-
securePair.encrypted.pipe(stream);
983-
securePair.cleartext.on('data', data => {
984-
this.packetParser.execute(data);
985-
});
986-
this.write = function(buffer) {
987-
securePair.cleartext.write(buffer);
988-
};
989-
securePair.on('secure', () => {
990-
onSecure(rejectUnauthorized ? securePair.ssl.verifyError() : null);
991-
});
992-
};
993-
}
994-
995932
module.exports = Connection;

test/builtin-runner/regressions/2052.test.mjs

+6-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ describe(
1010
() => {
1111
it('should report 0 actual parameters when 1 placeholder is used in ORDER BY ?', (t, done) => {
1212
const connection = {
13+
sequenceId: 1,
1314
constructor: {
1415
statementKey: () => 0,
1516
},
@@ -23,9 +24,10 @@ describe(
2324
},
2425
writePacket: (packet) => {
2526
// client -> server COM_PREPARE
27+
packet.writeHeader(1);
2628
assert.equal(
2729
packet.buffer.toString('hex'),
28-
'000000001673656c656374202a2066726f6d207573657273206f72646572206279203f'
30+
'1f0000011673656c656374202a2066726f6d207573657273206f72646572206279203f'
2931
);
3032
},
3133
};
@@ -68,7 +70,9 @@ describe(
6870
}
6971
);
7072

71-
describe('E2E Prepare result with number of parameters incorrectly reported by the server', { timeout: 1000 }, () => {
73+
describe('E2E Prepare result with number of parameters incorrectly reported by the server',
74+
{ timeout: 1000 },
75+
() => {
7276
let connection;
7377

7478
function isNewerThan8_0_22() {

test/common.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const config = {
1212
port: process.env.MYSQL_PORT || 3306
1313
};
1414

15-
if (process.env.MYSQL_USE_TLS) {
15+
if (process.env.MYSQL_USE_TLS === '1') {
1616
config.ssl = {
1717
rejectUnauthorized: false,
1818
ca: fs.readFileSync(
@@ -32,7 +32,7 @@ exports.waitDatabaseReady = function(callback) {
3232
const tryConnect = function() {
3333
const conn = exports.createConnection({ database: 'mysql', password: process.env.MYSQL_PASSWORD });
3434
conn.once('error', err => {
35-
if (err.code !== 'PROTOCOL_CONNECTION_LOST' && err.code !== 'ETIMEDOUT') {
35+
if (err.code !== 'PROTOCOL_CONNECTION_LOST' && err.code !== 'ETIMEDOUT' && err.code !== 'ECONNREFUSED') {
3636
console.log('Unexpected error waiting for connection', err);
3737
process.exit(-1);
3838
}
@@ -84,6 +84,7 @@ exports.createConnection = function(args) {
8484
typeCast: args && args.typeCast,
8585
namedPlaceholders: args && args.namedPlaceholders,
8686
connectTimeout: args && args.connectTimeout,
87+
ssl: (args && args.ssl) ?? config.ssl,
8788
};
8889

8990
const conn = driver.createConnection(params);
@@ -164,10 +165,12 @@ exports.createServer = function(onListening, handler) {
164165
const server = require('../index.js').createServer();
165166
server.on('connection', conn => {
166167
conn.on('error', () => {
167-
// we are here when client drops connection
168+
// server side of the connection
169+
// ignore disconnects
168170
});
171+
// remove ssl bit from the flags
169172
let flags = 0xffffff;
170-
flags = flags ^ ClientFlags.COMPRESS;
173+
flags = flags ^ (ClientFlags.COMPRESS | ClientFlags.SSL);
171174

172175
conn.serverHandshake({
173176
protocolVersion: 10,

test/integration/connection/test-disconnects.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ const server = common.createServer(
2323
// different host provided via MYSQL_HOST that identifies a real MySQL
2424
// server instance.
2525
host: 'localhost',
26-
port: server._port
26+
port: server._port,
27+
ssl: false
2728
});
2829
connection.query('SELECT 123', (err, _rows, _fields) => {
2930
if (err) {

test/integration/connection/test-protocol-errors.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ const server = common.createServer(
2222
// different host provided via MYSQL_HOST that identifies a real MySQL
2323
// server instance.
2424
host: 'localhost',
25-
port: server._port
25+
port: server._port,
26+
ssl: false
2627
});
2728
connection.query(query, (err, _rows, _fields) => {
2829
if (err) {

test/integration/connection/test-quit.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ const server = common.createServer(
2222
// different host provided via MYSQL_HOST that identifies a real MySQL
2323
// server instance.
2424
host: 'localhost',
25-
port: server._port
25+
port: server._port,
26+
ssl: false
2627
});
2728

2829
connection.query(queryCli, (err, _rows, _fields) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const common = require('../../common');
5+
const connection = common.createConnection();
6+
7+
connection.query(`SHOW STATUS LIKE 'Ssl_cipher'`, (err, rows) => {
8+
assert.ifError(err);
9+
if (process.env.MYSQL_USE_TLS === '1') {
10+
assert.equal(rows[0].Value.length > 0, true);
11+
} else {
12+
assert.deepEqual(rows, [{ Variable_name: 'Ssl_cipher', Value: '' }]);
13+
}
14+
connection.end();
15+
});

0 commit comments

Comments
 (0)