-
Notifications
You must be signed in to change notification settings - Fork 436
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
feat: add TDS8.0 Support for tedious #1522
Changes from 23 commits
4b27035
96c88cb
60c1a38
bf32f85
e458d12
2165a2a
3a9213a
f654831
39b1379
b31c287
eabc9d5
0fcbe8a
ef108d6
a448262
a6ffebf
fee6ee7
30f0ccd
7891302
cbe4089
76a146a
2252a43
88ef29f
7818752
53dd486
e5b4c6f
d5f2bdb
81c52a2
f675c79
04c76a7
c955670
3b8074d
f2e45ad
a6d414a
c2aa47f
47bd587
d579439
20ef404
c20367b
a9524dc
9d52330
1859352
c3e8b67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import crypto from 'crypto'; | ||
import os from 'os'; | ||
import * as tls from 'tls'; | ||
import { Socket } from 'net'; | ||
import dns from 'dns'; | ||
|
||
|
@@ -365,7 +366,7 @@ export interface InternalConnectionOptions { | |
enableImplicitTransactions: null | boolean; | ||
enableNumericRoundabort: null | boolean; | ||
enableQuotedIdentifier: null | boolean; | ||
encrypt: boolean; | ||
encrypt: string | boolean; | ||
encryptionKeyStoreProviders: KeyStoreProviderMap | undefined; | ||
fallbackToDefaultDb: boolean; | ||
instanceName: undefined | string; | ||
|
@@ -660,11 +661,12 @@ export interface ConnectionOptions { | |
enableQuotedIdentifier?: boolean; | ||
|
||
/** | ||
* A boolean determining whether or not the connection will be encrypted. Set to `true` if you're on Windows Azure. | ||
* A string value that can be only set to 'strict', which indicates the usage TDS 8.0 protocol. Otherwise, | ||
* a boolean determining whether or not the connection will be encrypted. | ||
* | ||
* (default: `false`) | ||
* (default: `true`) | ||
*/ | ||
encrypt?: boolean; | ||
encrypt?: string | boolean; | ||
|
||
/** | ||
* By default, if the database requested by [[database]] cannot be accessed, | ||
|
@@ -818,6 +820,10 @@ export interface ConnectionOptions { | |
*/ | ||
trustServerCertificate?: boolean; | ||
|
||
/** | ||
* | ||
*/ | ||
serverName?: string; | ||
/** | ||
* A boolean determining whether to return rows as arrays or key-value collections. | ||
* | ||
|
@@ -1479,10 +1485,11 @@ class Connection extends EventEmitter { | |
|
||
this.config.options.enableQuotedIdentifier = config.options.enableQuotedIdentifier; | ||
} | ||
|
||
if (config.options.encrypt !== undefined) { | ||
if (typeof config.options.encrypt !== 'boolean') { | ||
throw new TypeError('The "config.options.encrypt" property must be of type boolean.'); | ||
if (config.options.encrypt !== 'strict') { | ||
throw new TypeError('The "encrypt" property must be set to "strict", or of type boolean.'); | ||
} | ||
} | ||
|
||
this.config.options.encrypt = config.options.encrypt; | ||
|
@@ -1642,6 +1649,13 @@ class Connection extends EventEmitter { | |
this.config.options.trustServerCertificate = config.options.trustServerCertificate; | ||
} | ||
|
||
if (config.options.serverName !== undefined) { | ||
if (typeof config.options.serverName !== 'string') { | ||
throw new TypeError('The "config.options.serverName" property must be of type string.'); | ||
} | ||
this.config.options.serverName = config.options.serverName; | ||
} | ||
|
||
if (config.options.useColumnNames !== undefined) { | ||
if (typeof config.options.useColumnNames !== 'boolean') { | ||
throw new TypeError('The "config.options.useColumnNames" property must be of type boolean.'); | ||
|
@@ -1967,21 +1981,52 @@ class Connection extends EventEmitter { | |
|
||
connect(connectOpts, dns.lookup, signal).then((socket) => { | ||
process.nextTick(() => { | ||
socket.on('error', (error) => { this.socketError(error); }); | ||
socket.on('close', () => { this.socketClose(); }); | ||
socket.on('end', () => { this.socketEnd(); }); | ||
socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); | ||
const secureContext = tls.createSecureContext(this.secureContextOptions); | ||
if (this.config.options.encrypt === 'strict') { | ||
const encryptOptions = { | ||
host: this.config.server, | ||
socket: socket, | ||
ALPNProtocols: ['tds/8.0'], | ||
secureContext: secureContext, | ||
servername: this.config.options.serverName ? this.config.options.serverName : this.config.server, | ||
}; | ||
const encryptsocket = tls.connect(encryptOptions, () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What will happen if someone connects using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If user uses a strict to connect to a SQL server instance that does not support it, a socket error will be returned as : " Client network socket disconnected before secure TLS connection was established" |
||
socket = encryptsocket; | ||
|
||
socket.on('error', (error) => { this.socketError(error); }); | ||
socket.on('close', () => { this.socketClose(); }); | ||
socket.on('end', () => { this.socketEnd(); }); | ||
socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); | ||
|
||
this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); | ||
|
||
this.socket = socket; | ||
|
||
this.closed = false; | ||
this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); | ||
|
||
this.sendPreLogin(); | ||
this.transitionTo(this.STATE.SENT_PRELOGIN); | ||
}); | ||
|
||
console.log('using TDS 8.0 strict TLS encryption'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this might be some leftover log output. |
||
} else { | ||
socket.on('error', (error) => { this.socketError(error); }); | ||
socket.on('close', () => { this.socketClose(); }); | ||
socket.on('end', () => { this.socketEnd(); }); | ||
socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); | ||
|
||
this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); | ||
this.messageIo.on('secure', (cleartext) => { this.emit('secure', cleartext); }); | ||
this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); | ||
this.messageIo.on('secure', (cleartext) => { this.emit('secure', cleartext); }); | ||
|
||
this.socket = socket; | ||
this.socket = socket; | ||
|
||
this.closed = false; | ||
this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); | ||
this.closed = false; | ||
this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); | ||
|
||
this.sendPreLogin(); | ||
this.transitionTo(this.STATE.SENT_PRELOGIN); | ||
this.sendPreLogin(); | ||
this.transitionTo(this.STATE.SENT_PRELOGIN); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove this duplication? |
||
} | ||
}); | ||
}, (err) => { | ||
this.clearConnectTimer(); | ||
|
@@ -2231,10 +2276,12 @@ class Connection extends EventEmitter { | |
* @private | ||
*/ | ||
sendPreLogin() { | ||
const [ , major, minor, build ] = /^(\d+)\.(\d+)\.(\d+)/.exec(version) ?? [ '0.0.0', '0', '0', '0' ]; | ||
|
||
const [, major, minor, build] = /^(\d+)\.(\d+)\.(\d+)/.exec(version) ?? ['0.0.0', '0', '0', '0']; | ||
const payload = new PreloginPayload({ | ||
encrypt: this.config.options.encrypt, | ||
// If encrypt setting is set to 'strict', then we should have already done the encryption before calling | ||
// this function. Therefore, the encrypt will be set to false here. | ||
// Otherwise, we will set encrypt here based on the encrypt Boolean value from the configuration. | ||
encrypt: typeof this.config.options.encrypt === 'boolean' && this.config.options.encrypt, | ||
version: { major: Number(major), minor: Number(minor), build: Number(build), subbuild: 0 } | ||
}); | ||
|
||
|
@@ -3155,16 +3202,15 @@ Connection.prototype.STATE = { | |
if (preloginPayload.fedAuthRequired === 1) { | ||
this.fedAuthRequired = true; | ||
} | ||
|
||
if (preloginPayload.encryptionString === 'ON' || preloginPayload.encryptionString === 'REQ') { | ||
if ('strict' !== this.config.options.encrypt && (preloginPayload.encryptionString === 'ON' || preloginPayload.encryptionString === 'REQ')) { | ||
if (!this.config.options.encrypt) { | ||
this.emit('connect', new ConnectionError("Server requires encryption, set 'encrypt' config option to true.", 'EENCRYPT')); | ||
return this.close(); | ||
} | ||
|
||
try { | ||
this.transitionTo(this.STATE.SENT_TLSSSLNEGOTIATION); | ||
await this.messageIo.startTls(this.secureContextOptions, this.routingData?.server ?? this.config.server, this.config.options.trustServerCertificate); | ||
await this.messageIo.startTls(this.secureContextOptions, this.config.options.serverName ? this.config.options.serverName : this.routingData?.server ?? this.config.server, this.config.options.trustServerCertificate); | ||
} catch (err: any) { | ||
return this.socketError(err); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: We can use
/SECURITYMODE='SQL'
to enable mixed authentication on installation, and use/SAPWD='...'
to set the password. That would allow us to skip changing these settings (and the server restart).