diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 16c995d..e3adfa1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,16 +15,16 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node-version: [ 16.x, 18.x, 20.x ] + node-version: [ 18.x, 20.x, 22.x ] os: [ windows-latest, ubuntu-latest, macOS-latest ] # Go steps: - name: Check out repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} @@ -48,7 +48,7 @@ jobs: - name: Notify uses: sarisia/actions-status-discord@v1 # Only fire alert once - if: github.ref == 'refs/heads/main' && failure() && matrix.node-version == '14.x' && matrix.os == 'ubuntu-latest' + if: github.ref == 'refs/heads/main' && failure() && matrix.node-version == '22.x' && matrix.os == 'ubuntu-latest' with: webhook: ${{ secrets.DISCORD_WEBHOOK }} title: "build and test" @@ -67,10 +67,10 @@ jobs: # Go steps: - name: Check out repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: lts/* registry-url: https://registry.npmjs.org/ diff --git a/changelog.md b/changelog.md index f49a4fd..62b4358 100644 --- a/changelog.md +++ b/changelog.md @@ -12,10 +12,15 @@ Hello! After a bit of a hiatus, the [Architect team](https://github.com/architec - Added `--verbose|-v` / `options.verbose`, `--debug|-d` / `options.debug` logging modes, and some additional logging - Added `-h` help alias for CLI - Added Typescript Types via JSDOC comments +- Enabled `port` and `host` options to be passed to Dynalite in `http` mode (instead of needing to be set in `dynalite.listen(port, host)`) + ### Changed -- [Breaking change] Introduced minimum Node.js version of >= 16; fixes [#169](https://github.com/architect/dynalite/issues/169) +- [Breaking change] Introduced minimum Node.js version of >= 18; fixes [#169](https://github.com/architect/dynalite/issues/169) +- [Breaking change] When using SSL mode, you must now supply your own `key`, `cert`, and `ca` (which isn't much of a breaking change, really, because Dynalite's certs were expired); fixes [#176](https://github.com/architect/dynalite/issues/176) + - In CLI mode, pass them as file paths with flags (e.g. `--key /foo/key.pem --cert /foo/cert.pem --ca /foo/ca-cert.pem`) + - As a module, you can pass them as strings or as file paths; when passing as file paths, make sure you include a boolean `useSSLFilePaths` option - Changed license from MIT to Apache 2.0; see [#166](https://github.com/architect/dynalite/issues/166) - Updated dependencies (which themselves dropped support for older versions of Node.js) - Updated tests diff --git a/cli.js b/cli.js index 82b0894..618e3bf 100755 --- a/cli.js +++ b/cli.js @@ -16,6 +16,7 @@ if (argv.help || argv.h) { '--port The port to listen on (default: 4567)', '--path The path to use for the LevelDB store (in-memory by default)', '--ssl Enable SSL for the web server (default: false)', + ' Pass cert file paths with --key, --cert, and --ca', '--createTableMs Amount of time tables stay in CREATING state (default: 500)', '--deleteTableMs Amount of time tables stay in DELETING state (default: 500)', '--updateTableMs Amount of time tables stay in UPDATING state (default: 500)', diff --git a/index.js b/index.js index 71e7cbf..899d2a3 100644 --- a/index.js +++ b/index.js @@ -1,16 +1,13 @@ -var http = require('http'), - https = require('https'), - fs = require('fs'), - path = require('path'), - url = require('url'), - crypto = require('crypto'), - crc32 = require('buffer-crc32'), - validations = require('./validations'), - db = require('./db') - -var MAX_REQUEST_BYTES = 16 * 1024 * 1024 -var verbose = false -var debug = false +let { readFileSync } = require('fs') +let url = require('url') +let crypto = require('crypto') +let crc32 = require('buffer-crc32') +let validations = require('./validations') +let db = require('./db') + +const MAX_REQUEST_BYTES = 16 * 1024 * 1024 +let verbose = false +let debug = false var validApis = [ 'DynamoDB_20111205', 'DynamoDB_20120810' ], validOperations = [ 'BatchGetItem', 'BatchWriteItem', 'CreateTable', 'DeleteItem', 'DeleteTable', @@ -19,16 +16,15 @@ var validApis = [ 'DynamoDB_20111205', 'DynamoDB_20120810' ], actions = {}, actionValidations = {} -module.exports = dynalite - /** * @param {Object} options - The shape is the same as SpecialType above * @param {boolean} [options.verbose=false] - Enable verbose logging * @param {boolean} [options.debug=false] - Enable debug logging * @param {boolean} [options.ssl=false] - Enable SSL for the web server - * @param {string} [options.key] - SSL private key - if omitted and ssl enabled, self-signed cert will be used - * @param {string} [options.cert] - SSL certificate - if omitted and ssl enabled, self-signed cert will be used - * @param {string} [options.ca] - SSL certificate authority - if omitted and ssl enabled, self-signed cert will be used + * @param {string} [options.key] - SSL private key; to use a file path instead, pass the useSSLFilePaths option + * @param {string} [options.cert] - SSL certificate; to use a file path instead, pass the useSSLFilePaths option + * @param {string} [options.ca] - SSL certificate authority; to use a file path instead, pass the useSSLFilePaths option + * @param {boolean} [options.useSSLFilePaths] - Use file paths instead of strings for SSL certs * @param {number} [options.createTableMs=500] - Amount of time tables stay in CREATING state * @param {number} [options.deleteTableMs=500] - Amount of time tables stay in DELETING state * @param {number} [options.updateTableMs=500] - Amount of time tables stay in UPDATING state @@ -45,17 +41,28 @@ function dynalite (options) { var server, store = db.create(options), requestHandler = httpHandler.bind(null, store) if (options.ssl) { - options.key = options.key || fs.readFileSync(path.join(__dirname, 'ssl', 'server-key.pem')) - options.cert = options.cert || fs.readFileSync(path.join(__dirname, 'ssl', 'server-crt.pem')) - options.ca = options.ca || fs.readFileSync(path.join(__dirname, 'ssl', 'ca-crt.pem')) + let useFilePath = options.useSSLFilePaths || options._ + let files = [ 'key', 'cert', 'ca' ] + files.forEach(file => { + if (!options[file]) throw ReferenceError(`SSL must have '${file}' option`) + }) + options.key = useFilePath ? readFileSync(options.key) : options.key + options.cert = useFilePath ? readFileSync(options.cert) : options.cert + options.ca = useFilePath ? readFileSync(options.ca) : options.ca + + // eslint-disable-next-line + let https = require('https') server = https.createServer(options, requestHandler) } else { - server = http.createServer(requestHandler) + // eslint-disable-next-line + let http = require('http') + server = http.createServer(options, requestHandler) } // Ensure we close DB when we're closing the server too - var httpServerClose = server.close, httpServerListen = server.listen + let httpServerClose = server.close + let httpServerListen = server.listen server.close = function (cb) { store.db.close(function (err) { if (err) return cb(err) @@ -303,4 +310,5 @@ function httpHandler (store, req, res) { }) } +module.exports = dynalite if (require.main === module) dynalite().listen(4567) diff --git a/package.json b/package.json index 7c641b9..99c8873 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "lint": "eslint . --fix" }, "engines": { - "node": ">=16" + "node": ">=18" }, "author": "Michael Hart ", "license": "Apache-2.0", @@ -40,7 +40,7 @@ "@architect/eslint-config": "^2.1.1", "aws4": "^1.12.0", "eslint": "^8.48.0", - "mocha": "^10.2.0", + "mocha": "^11.1.0", "pegjs": "^0.10.0", "should": "^13.2.3" }, diff --git a/readme.md b/readme.md index bceb94b..3f2502d 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# dynalite +# Dynalite [![GitHub CI status](https://github.com/architect/dynalite/workflows/Node%20CI/badge.svg)](https://github.com/architect/dynalite/actions?query=workflow%3A%22Node+CI%22) @@ -9,6 +9,7 @@ for fast in-memory or persistent usage. This project aims to match the live DynamoDB instances as closely as possible (and is tested against them in various regions), including all limits and error messages. + ## What about Amazon's DynamoDB Local? This project was created before DynamoDB Local existed, and when it did, it differed a lot from the live instances @@ -17,6 +18,7 @@ and is probably more up-to-date than dynalite is. I'd recommend using it over dy overhead of starting the JVM (or docker) each time. If you need a fast in-memory option that you can start up in milliseconds, then dynalite might be more suitable for you. + ## Example ```sh @@ -32,6 +34,7 @@ Options: --port The port to listen on (default: 4567) --path The path to use for the LevelDB store (in-memory by default) --ssl Enable SSL for the web server (default: false) + Pass cert file paths with --key, --cert, and --ca --createTableMs Amount of time tables stay in CREATING state (default: 500) --deleteTableMs Amount of time tables stay in DELETING state (default: 500) --updateTableMs Amount of time tables stay in UPDATING state (default: 500) @@ -46,8 +49,8 @@ Or programmatically: ```js // Returns a standard Node.js HTTP server -var dynalite = require('dynalite') -var dynaliteServer = dynalite({ path: './mydb', createTableMs: 50 }) +const dynalite = require('dynalite') +const dynaliteServer = dynalite() // Listen on port 4567 dynaliteServer.listen(4567, function(err) { @@ -56,17 +59,30 @@ dynaliteServer.listen(4567, function(err) { }) ``` +```js +// Use Dynalite in HTTPS mode +const dynalite = require('dynalite') +const dynaliteServer = dynalite({ + ssl: true, + key: 'Your private key in PEM format...', + cert: 'Your cert chain in PEM format...', + ca: 'Your CA certificate string...', + // Alternately, pass `useSSLFilePaths: true` to use file paths for `key`, `cert`, and `ca` +}) +``` + Once running, here's how you use the [AWS SDK](https://github.com/aws/aws-sdk-js) to connect (after [configuring the SDK](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/configuring-the-jssdk.html)): ```js -var AWS = require('aws-sdk') +const AWS = require('aws-sdk') -var dynamo = new AWS.DynamoDB({ endpoint: 'http://localhost:4567' }) +const dynamo = new AWS.DynamoDB({ endpoint: 'http://localhost:4567' }) dynamo.listTables(console.log.bind(console)) ``` + ## Installation With [npm](https://www.npmjs.com/), to install the CLI: diff --git a/ssl/ca-key.pem b/ssl/ca-key.pem deleted file mode 100644 index 1213eae..0000000 --- a/ssl/ca-key.pem +++ /dev/null @@ -1,54 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,BA5EBAEF10CAD318 - -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 ------END RSA PRIVATE KEY----- diff --git a/ssl/server-csr.pem b/ssl/server-csr.pem deleted file mode 100644 index 5183ce0..0000000 --- a/ssl/server-csr.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIEsTCCApkCAQAwUzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMREwDwYDVQQH -DAhOZXcgWW9yazERMA8GA1UECgwIRHluYWxpdGUxETAPBgNVBAMMCGR5bmFsaXRl -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyJ8p54UUgsIJCrYodYxu -zMEbltd8UQDx2KwU9ZPFg1YLVG14gqBu12Hx39aVGE2K5xj4V0BwJBV3Aqlix4bg -ZLVRTU3NW320cbreS/5VaTOizy3ruSq49kXXMfKhlWJzmgg8Enp8loJEmsTBoW2i -DjHDam68XQDOTXajJLUyvD3MQVi6VMRsxYBkUhTMiPnmlz3g2ypAlTjweOJNKlmo -F/mDISgCMvAt8Jao+qgLntxm+VgTtiLMzUi8PIEWk7RAhQyTBzwAiW0eoyo8sP6w -5B+rq3EQVix/DbnxddNeaMYScAYiu3Ttx3tuOPY8o0YlW+KG/JJh1mNAzCmnKp1V -x54GV/yg2D8la9UJzwZnfYtgziyE54pikPIkIFoWTgG0iKsucJcXz4oKR/1f30S3 -5aIXG66MrzBJBpJ9yDt9WhOONA4vuQa1BS+yi8/9EtFfSZKnkmKs68fg3JsO7kCs -zjjc3F9SdKUCHVwvLRCs4mX840A3EbGrhalv47f8XQTwXwkjK/nhLNIG49SVSAx9 -LJ5nbKbSpFTwmuWjL5m5XhM5pyL5naTAAF4BzQbIPiBVTYwZtKJLcCqLZ+MQQKxm -fbanbD3OdUMH8LDKctWKz2GQGQvpTHsPhQ/6+c606TJ1ib5BJ6VbIfPEwXM0Tg0O -z7IQZpbnj0fnDX3Qo76SFWUCAwEAAaAZMBcGCSqGSIb3DQEJBzEKDAhwYXNzd29y -ZDANBgkqhkiG9w0BAQUFAAOCAgEAnXofmdIxl1UqP5qNZbpDXDB+fMKi6qdGBpT7 -sdgw0Ahq0f2W66MyzGLBwZC/rDAtTuMC5PHsw+V3ViqahB2ZXTpY2J2g2HdxBvus -JnFfAhyoWtHpqSgmQCQvgVlanCyb7Zs4bqQOxU2xAURWXby/+fE18oOX3zsgn5wZ -wMpavqNRIi070+4PXUk4OyCaUzG/QxsWrA4EFZrzChxD1YT4n0pwwQocxvUlfAUH -duKfip1C/3Amd9QIaD4VWqp3lSaR5x1JnGInzlXm4m0kHJRjFVKAH4NBOeeuGjMD -fV5MHQTIp1ragkY57225A6QNRZ5ZVDcecIoAzFAOxJBLPbd8ciT1UyjIWOY6xMGs -9Y6aiR2BpjupNVW9rhgfL/y5tmIZJt+s2NAobCeTYMeNbqzut8WS5XKMCDQuxB3M -ULBJ8hvdzQ5reQcbSZPTMVcrb01ahBsP2c0bxlup/J36BYLSu+1AL0Shc426kosQ -Y1LdVoti6+JF086UagzkqHQmwWJETNrO5CHnfv/nDCTOjTvTTJ50LZ5ti2JCDtmo -RcdTBaFLLfEsy5mb8iAM8hemPulR00od4kmUp6botLH/QVsUkYsDMze3kkucvM2L -I2f8oNeT9lhs8gHqCNXFwP6oD3KACOdCM2PG4XDzllEYlz5VcshsDZ7LxcJEYR/l -S51P1iQ= ------END CERTIFICATE REQUEST----- diff --git a/test/connection.js b/test/connection.js index 77de3d0..ed75e59 100644 --- a/test/connection.js +++ b/test/connection.js @@ -1,7 +1,8 @@ var https = require('https'), once = require('once'), dynalite = require('..'), - helpers = require('./helpers') + helpers = require('./helpers'), + path = require('path') var request = helpers.request @@ -109,7 +110,13 @@ describe('dynalite connections', function () { }) it('should connect to SSL', function (done) { - var port = 10000 + Math.round(Math.random() * 10000), dynaliteServer = dynalite({ ssl: true }) + var port = 10000 + Math.round(Math.random() * 10000), dynaliteServer = dynalite({ + ssl: true, + useSSLFilePaths: true, + key: path.join(__dirname, 'ssl', 'server-key.pem'), + cert: path.join(__dirname, 'ssl', 'server-crt.pem'), + ca: path.join(__dirname, 'ssl', 'ca-crt.pem'), + }) dynaliteServer.listen(port, function (err) { if (err) return done(err) diff --git a/ssl/ca-crt.pem b/test/ssl/ca-crt.pem similarity index 100% rename from ssl/ca-crt.pem rename to test/ssl/ca-crt.pem diff --git a/ssl/server-crt.pem b/test/ssl/server-crt.pem similarity index 100% rename from ssl/server-crt.pem rename to test/ssl/server-crt.pem diff --git a/ssl/server-key.pem b/test/ssl/server-key.pem similarity index 100% rename from ssl/server-key.pem rename to test/ssl/server-key.pem