Support CA fingerprint validation (#1499)
Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com> Co-authored-by: Ioannis Kakavas <ioannis@elastic.co>
This commit is contained in:
committed by
GitHub
parent
b0a7a21f72
commit
2d1505eb2b
@ -77,6 +77,7 @@ test('Should update the connection pool', t => {
|
||||
t.same(hosts[i], {
|
||||
url: new URL(nodes[id].url),
|
||||
id: id,
|
||||
caFingerprint: null,
|
||||
roles: {
|
||||
master: true,
|
||||
data: true,
|
||||
|
||||
@ -26,6 +26,7 @@ const intoStream = require('into-stream')
|
||||
const { ConnectionPool, Transport, Connection, errors } = require('../../index')
|
||||
const { CloudConnectionPool } = require('../../lib/pool')
|
||||
const { Client, buildServer } = require('../utils')
|
||||
|
||||
let clientVersion = require('../../package.json').version
|
||||
if (clientVersion.includes('-')) {
|
||||
clientVersion = clientVersion.slice(0, clientVersion.indexOf('-')) + 'p'
|
||||
@ -1498,3 +1499,88 @@ test('Bearer auth', t => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Check server fingerprint (success)', t => {
|
||||
t.plan(1)
|
||||
|
||||
function handler (req, res) {
|
||||
res.end('ok')
|
||||
}
|
||||
|
||||
buildServer(handler, { secure: true }, ({ port, caFingerprint }, server) => {
|
||||
const client = new Client({
|
||||
node: `https://localhost:${port}`,
|
||||
caFingerprint
|
||||
})
|
||||
|
||||
client.info((err, res) => {
|
||||
t.error(err)
|
||||
server.stop()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Check server fingerprint (failure)', t => {
|
||||
t.plan(2)
|
||||
|
||||
function handler (req, res) {
|
||||
res.end('ok')
|
||||
}
|
||||
|
||||
buildServer(handler, { secure: true }, ({ port }, server) => {
|
||||
const client = new Client({
|
||||
node: `https://localhost:${port}`,
|
||||
caFingerprint: 'FO:OB:AR'
|
||||
})
|
||||
|
||||
client.info((err, res) => {
|
||||
t.ok(err instanceof errors.ConnectionError)
|
||||
t.equal(err.message, 'Server certificate CA fingerprint does not match the value configured in caFingerprint')
|
||||
server.stop()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('caFingerprint can\'t be configured over http / 1', t => {
|
||||
t.plan(2)
|
||||
|
||||
try {
|
||||
new Client({ // eslint-disable-line
|
||||
node: 'http://localhost:9200',
|
||||
caFingerprint: 'FO:OB:AR'
|
||||
})
|
||||
t.fail('shuld throw')
|
||||
} catch (err) {
|
||||
t.ok(err instanceof errors.ConfigurationError)
|
||||
t.equal(err.message, 'You can\'t configure the caFingerprint with a http connection')
|
||||
}
|
||||
})
|
||||
|
||||
test('caFingerprint can\'t be configured over http / 2', t => {
|
||||
t.plan(2)
|
||||
|
||||
try {
|
||||
new Client({ // eslint-disable-line
|
||||
nodes: ['http://localhost:9200'],
|
||||
caFingerprint: 'FO:OB:AR'
|
||||
})
|
||||
t.fail('should throw')
|
||||
} catch (err) {
|
||||
t.ok(err instanceof errors.ConfigurationError)
|
||||
t.equal(err.message, 'You can\'t configure the caFingerprint with a http connection')
|
||||
}
|
||||
})
|
||||
|
||||
test('caFingerprint can\'t be configured over http / 3', t => {
|
||||
t.plan(1)
|
||||
|
||||
try {
|
||||
new Client({ // eslint-disable-line
|
||||
nodes: ['https://localhost:9200'],
|
||||
caFingerprint: 'FO:OB:AR'
|
||||
})
|
||||
t.pass('should not throw')
|
||||
} catch (err) {
|
||||
t.fail('shuld not throw')
|
||||
}
|
||||
})
|
||||
|
||||
@ -28,7 +28,8 @@ const hpagent = require('hpagent')
|
||||
const intoStream = require('into-stream')
|
||||
const { buildServer } = require('../utils')
|
||||
const Connection = require('../../lib/Connection')
|
||||
const { TimeoutError, ConfigurationError, RequestAbortedError } = require('../../lib/errors')
|
||||
const { TimeoutError, ConfigurationError, RequestAbortedError, ConnectionError } = require('../../lib/errors')
|
||||
const { getIssuerCertificate } = Connection.internals
|
||||
|
||||
test('Basic (http)', t => {
|
||||
t.plan(4)
|
||||
@ -947,3 +948,139 @@ test('Abort with a slow body', t => {
|
||||
|
||||
setImmediate(() => request.abort())
|
||||
})
|
||||
|
||||
test('Check server fingerprint (success)', t => {
|
||||
t.plan(2)
|
||||
|
||||
function handler (req, res) {
|
||||
res.end('ok')
|
||||
}
|
||||
|
||||
buildServer(handler, { secure: true }, ({ port, caFingerprint }, server) => {
|
||||
const connection = new Connection({
|
||||
url: new URL(`https://localhost:${port}`),
|
||||
caFingerprint
|
||||
})
|
||||
|
||||
connection.request({
|
||||
path: '/hello',
|
||||
method: 'GET'
|
||||
}, (err, res) => {
|
||||
t.error(err)
|
||||
|
||||
let payload = ''
|
||||
res.setEncoding('utf8')
|
||||
res.on('data', chunk => { payload += chunk })
|
||||
res.on('error', err => t.fail(err))
|
||||
res.on('end', () => {
|
||||
t.equal(payload, 'ok')
|
||||
server.stop()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Check server fingerprint (failure)', t => {
|
||||
t.plan(2)
|
||||
|
||||
function handler (req, res) {
|
||||
res.end('ok')
|
||||
}
|
||||
|
||||
buildServer(handler, { secure: true }, ({ port }, server) => {
|
||||
const connection = new Connection({
|
||||
url: new URL(`https://localhost:${port}`),
|
||||
caFingerprint: 'FO:OB:AR'
|
||||
})
|
||||
|
||||
connection.request({
|
||||
path: '/hello',
|
||||
method: 'GET'
|
||||
}, (err, res) => {
|
||||
t.ok(err instanceof ConnectionError)
|
||||
t.equal(err.message, 'Server certificate CA fingerprint does not match the value configured in caFingerprint')
|
||||
server.stop()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('getIssuerCertificate returns the root CA', t => {
|
||||
t.plan(2)
|
||||
const issuerCertificate = {
|
||||
fingerprint256: 'BA:ZF:AZ',
|
||||
subject: {
|
||||
C: '1',
|
||||
ST: '1',
|
||||
L: '1',
|
||||
O: '1',
|
||||
OU: '1',
|
||||
CN: '1'
|
||||
},
|
||||
issuer: {
|
||||
C: '1',
|
||||
ST: '1',
|
||||
L: '1',
|
||||
O: '1',
|
||||
OU: '1',
|
||||
CN: '1'
|
||||
}
|
||||
}
|
||||
issuerCertificate.issuerCertificate = issuerCertificate
|
||||
|
||||
const socket = {
|
||||
getPeerCertificate (bool) {
|
||||
t.ok(bool)
|
||||
return {
|
||||
fingerprint256: 'FO:OB:AR',
|
||||
subject: {
|
||||
C: '1',
|
||||
ST: '1',
|
||||
L: '1',
|
||||
O: '1',
|
||||
OU: '1',
|
||||
CN: '1'
|
||||
},
|
||||
issuer: {
|
||||
C: '2',
|
||||
ST: '2',
|
||||
L: '2',
|
||||
O: '2',
|
||||
OU: '2',
|
||||
CN: '2'
|
||||
},
|
||||
issuerCertificate
|
||||
}
|
||||
}
|
||||
}
|
||||
t.same(getIssuerCertificate(socket), issuerCertificate)
|
||||
})
|
||||
|
||||
test('getIssuerCertificate detects invalid/malformed certificates', t => {
|
||||
t.plan(2)
|
||||
const socket = {
|
||||
getPeerCertificate (bool) {
|
||||
t.ok(bool)
|
||||
return {
|
||||
fingerprint256: 'FO:OB:AR',
|
||||
subject: {
|
||||
C: '1',
|
||||
ST: '1',
|
||||
L: '1',
|
||||
O: '1',
|
||||
OU: '1',
|
||||
CN: '1'
|
||||
},
|
||||
issuer: {
|
||||
C: '2',
|
||||
ST: '2',
|
||||
L: '2',
|
||||
O: '2',
|
||||
OU: '2',
|
||||
CN: '2'
|
||||
}
|
||||
// missing issuerCertificate
|
||||
}
|
||||
}
|
||||
}
|
||||
t.equal(getIssuerCertificate(socket), null)
|
||||
})
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
const debug = require('debug')('elasticsearch-test')
|
||||
const stoppable = require('stoppable')
|
||||
|
||||
@ -35,6 +36,13 @@ const secureOpts = {
|
||||
cert: readFileSync(join(__dirname, '..', 'fixtures', 'https.cert'), 'utf8')
|
||||
}
|
||||
|
||||
const caFingerprint = getFingerprint(secureOpts.cert
|
||||
.split('\n')
|
||||
.slice(1, -1)
|
||||
.map(line => line.trim())
|
||||
.join('')
|
||||
)
|
||||
|
||||
let id = 0
|
||||
function buildServer (handler, opts, cb) {
|
||||
const serverId = id++
|
||||
@ -58,7 +66,7 @@ function buildServer (handler, opts, cb) {
|
||||
server.listen(0, () => {
|
||||
const port = server.address().port
|
||||
debug(`Server '${serverId}' booted on port ${port}`)
|
||||
resolve([Object.assign({}, secureOpts, { port }), server])
|
||||
resolve([Object.assign({}, secureOpts, { port, caFingerprint }), server])
|
||||
})
|
||||
})
|
||||
} else {
|
||||
@ -70,4 +78,11 @@ function buildServer (handler, opts, cb) {
|
||||
}
|
||||
}
|
||||
|
||||
function getFingerprint (content, inputEncoding = 'base64', outputEncoding = 'hex') {
|
||||
const shasum = crypto.createHash('sha256')
|
||||
shasum.update(content, inputEncoding)
|
||||
const res = shasum.digest(outputEncoding)
|
||||
return res.toUpperCase().match(/.{1,2}/g).join(':')
|
||||
}
|
||||
|
||||
module.exports = buildServer
|
||||
|
||||
Reference in New Issue
Block a user