@small-tech/node-pebble
Version:
A Node.js wrapper for Let’s Encrypt’s Pebble (“a small RFC 8555 ACME test server not suited for a production certificate authority”).
151 lines (125 loc) • 4.67 kB
JavaScript
/**
npm post-install script
Downloads, installs, and configures Pebble.
*/
import os from 'os'
import fs from 'fs'
import path from 'path'
import https from 'https'
import { extract } from 'tar'
const __dirname = new URL('.', import.meta.url).pathname
/**
Gets resource at passed URL string using HTTPS.
@param { string } url
*/
async function secureGet (url) {
return new Promise((resolve, reject) => {
https.get(url, response => {
const statusCode = response.statusCode
const location = response.headers.location
// Reject if it’s not one of the status codes we are testing.
if (statusCode !== 200 && statusCode !== 302) {
reject({statusCode})
}
let body = ''
response.on('data', _ => body += _)
response.on('end', () => {
resolve({statusCode, location, body})
})
})
})
}
/**
Streams resource at passed URL string to file at passed file path.
@param { string } url
@param { string } filePath
*/
async function secureStreamToFile (url, filePath) {
return new Promise((resolve, reject) => {
const fileStream = fs.createWriteStream(filePath)
https.get(url, response => {
response.pipe(fileStream)
fileStream.on('finish', () => {
fileStream.close()
resolve()
})
fileStream.on('error', error => {
fs.unlinkSync(filePath)
reject(error)
})
})
})
}
//
// Sanity check: ensure we’re on a supported platform (Linux, macOS, or Windows) and bail if not.
//
const _platform = os.platform()
if (_platform !== 'linux' && _platform !== 'darwin' && _platform !== 'win32' ) {
throw new Error(`Node Pebble Error: unsupported platform (only Linux, macOS, and Windows are supported, not ${_platform}).`)
}
//
// Pebble configuration file (adds shortlived profile).
//
const configuration = `{
"pebble": {
"listenAddress": "0.0.0.0:14000",
"managementListenAddress": "0.0.0.0:15000",
"certificate": "test/certs/localhost/cert.pem",
"privateKey": "test/certs/localhost/key.pem",
"httpPort": 80,
"tlsPort": 443,
"externalAccountBindingRequired": false,
"profiles": {
"default": {
"description": "The profile you know and love",
"validityPeriod": 7776000
},
"shortlived": {
"description": "A short-lived cert profile, without actual enforcement",
"validityPeriod": 518400
}
}
}
}
`
//
// Install the Pebble binary.
//
const PEBBLE_VERSION = 'v2.9.0'
const architecture = os.arch()
const platform = _platform === 'win32' ? 'windows' : _platform
const binaryExtension = _platform === 'win32' ? '.exe' : ''
const binaryName = `pebble${binaryExtension}`
const binaryPath = path.join(__dirname, binaryName)
const configurationFilePath = path.join(__dirname, 'test', 'config', 'pebble-config.json')
const downloadFileName = `pebble-${platform}-${architecture === 'x64' ? 'amd64' : architecture}.tar.gz`
const downloadUrl = `https://github.com/letsencrypt/pebble/releases/download/${PEBBLE_VERSION}/${downloadFileName}`
const archivePath = path.join(__dirname, downloadFileName)
console.log(' Node Pebble (postinstall)')
console.log(' ────────────────────────────────────────────────────────────────────────')
process.stdout.write(` ╰─ Removing old Pebble archive and binary (if any)… `)
fs.rmSync(archivePath, {force: true})
fs.rmSync(binaryPath, {force: true})
process.stdout.write('done.\n')
process.stdout.write(` ╰─ Downloading Pebble ${PEBBLE_VERSION} archive… `)
const binaryRedirectUrl = (await secureGet(downloadUrl)).location
await secureStreamToFile(binaryRedirectUrl, archivePath)
process.stdout.write('done.\n')
process.stdout.write(` ╰─ Unarchiving Pebble archive… `)
await extract({
cwd: __dirname,
file: archivePath,
strip: 3
})
process.stdout.write('done.\n')
process.stdout.write(` ╰─ Making the binary executable… `)
fs.chmodSync(binaryPath, 0o755)
process.stdout.write('done.\n')
process.stdout.write(` ╰─ Configuring Pebble… `)
fs.writeFileSync(configurationFilePath, configuration)
process.stdout.write('done.\n')
process.stdout.write(` ╰─ Deleting Pebble archive… `)
fs.rmSync(archivePath)
process.stdout.write('done.\n')
console.log(' ────────────────────────────────────────────────────────────────────────')