From f54901a37d3fb67315b4d43862ec4517acc3c3b7 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Wed, 17 Aug 2022 09:20:18 +0000 Subject: [PATCH 01/23] Added services and vscode service on agent --- agent/package-lock.json | 706 ++++++++++++++++++++++++++ agent/package.json | 2 + agent/src/Agent.ts | 30 +- agent/src/lib/services/baseService.ts | 57 +++ agent/src/lib/services/vscode.ts | 19 + agent/src/lib/typing/types.ts | 6 + agent/src/lib/utils.ts | 7 +- agent/src/ws/MessageRouting.ts | 58 +-- 8 files changed, 832 insertions(+), 53 deletions(-) create mode 100644 agent/src/lib/services/baseService.ts create mode 100644 agent/src/lib/services/vscode.ts diff --git a/agent/package-lock.json b/agent/package-lock.json index 374638c..71a1d1b 100644 --- a/agent/package-lock.json +++ b/agent/package-lock.json @@ -30,9 +30,23 @@ "@typescript-eslint/eslint-plugin": "^5.11.0", "@typescript-eslint/parser": "^5.11.0", "eslint": "^8.9.0", + "nodemon": "^2.0.19", + "ts-node": "^10.9.1", "typescript": "4.5" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", @@ -82,6 +96,31 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -117,6 +156,30 @@ "node": ">= 8" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -338,6 +401,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "node_modules/acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", @@ -359,6 +428,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -404,6 +482,25 @@ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -431,6 +528,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -478,6 +584,45 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -514,6 +659,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -567,6 +718,15 @@ "node": ">=0.4.0" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -938,6 +1098,20 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -1034,6 +1208,12 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -1075,6 +1255,18 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1192,6 +1384,12 @@ "node": ">=10" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1301,6 +1499,98 @@ "nan": "^2.14.0" } }, + "node_modules/nodemon": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", + "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1403,6 +1693,12 @@ "node": ">= 0.8.0" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -1440,6 +1736,18 @@ "mz": "^2.7.0" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -1561,6 +1869,27 @@ "node": ">=8" } }, + "node_modules/simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -1664,11 +1993,66 @@ "node": ">=8.0" } }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -1727,6 +2111,12 @@ "node": ">=4.2.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -1750,6 +2140,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -1827,9 +2223,27 @@ "engines": { "node": ">= 14" } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } }, "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, "@eslint/eslintrc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", @@ -1872,6 +2286,28 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1898,6 +2334,30 @@ "fastq": "^1.6.0" } }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -2030,6 +2490,12 @@ "eslint-visitor-keys": "^3.0.0" } }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", @@ -2043,6 +2509,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2075,6 +2547,22 @@ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2099,6 +2587,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2134,6 +2628,33 @@ "supports-color": "^7.1.0" } }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2164,6 +2685,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2200,6 +2727,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2491,6 +3024,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -2560,6 +3100,12 @@ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -2592,6 +3138,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2686,6 +3241,12 @@ "yallist": "^4.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2768,6 +3329,71 @@ "nan": "^2.14.0" } }, + "nodemon": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", + "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2843,6 +3469,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -2863,6 +3495,15 @@ "mz": "^2.7.0" } }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -2933,6 +3574,23 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3009,11 +3667,41 @@ "is-number": "^7.0.0" } }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -3050,6 +3738,12 @@ "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -3070,6 +3764,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -3121,6 +3821,12 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/agent/package.json b/agent/package.json index 8185064..2ce76f7 100644 --- a/agent/package.json +++ b/agent/package.json @@ -40,6 +40,8 @@ "@typescript-eslint/eslint-plugin": "^5.11.0", "@typescript-eslint/parser": "^5.11.0", "eslint": "^8.9.0", + "nodemon": "^2.0.19", + "ts-node": "^10.9.1", "typescript": "4.5" }, "bin": "./dist/app.js" diff --git a/agent/src/Agent.ts b/agent/src/Agent.ts index bf4afed..ad7e03e 100644 --- a/agent/src/Agent.ts +++ b/agent/src/Agent.ts @@ -1,6 +1,6 @@ import { AgentConfiguration } from './lib/typing/types'; import { RawData, WebSocket } from 'ws'; -import { printSuccess } from './lib/utils'; +import { generatePassword, printSuccess } from './lib/utils'; import log4js, { Logger } from 'log4js'; import { handleMessage } from './ws/handleMessage'; import { wsCommand } from './ws/wsCommand'; @@ -9,6 +9,8 @@ import passwd from 'passwd-linux'; import { MessageDataResponse } from './lib/typing/MessageData'; import WebShell from './WebShell/WebShell'; import Ticket from './lib/typing/Ticket'; +import Service from './lib/services/baseService'; +import VSCode from './lib/services/vscode'; export default class Agent { public ws: WebSocket; @@ -19,11 +21,18 @@ export default class Agent { private webShell: WebShell; protected handleMessage: (message: RawData) => void = handleMessage; protected wsCommand: (commandData: Task) => void = wsCommand; + public serviceJournal: Map; + + private services: { [key: string]: Service } = { + vsCode: new VSCode(), + }; constructor(public config: AgentConfiguration) { this.configureLogger(); this.initWebSocket(); this.startWebShell(); + this.serviceJournal = new Map(); + this.runServices(); } private initWebSocket(isReconnection = false): void { @@ -31,7 +40,8 @@ export default class Agent { this.ws.on('open', () => { printSuccess( - `${isReconnection ? 'Rec' : 'C'}onnected to ${this.config.socketServer + `${isReconnection ? 'Rec' : 'C'}onnected to ${ + this.config.socketServer }` ); }); @@ -51,7 +61,9 @@ export default class Agent { if (err.message.includes('connect ECONNREFUSED')) { this.logger.error(err.message); this.reconnectToWS(); - } else if (err.message === 'unable to verify the first certificate') { + } else if ( + err.message === 'unable to verify the first certificate' + ) { this.logger.error(err.message); this.reconnectToWS(); } @@ -109,4 +121,16 @@ export default class Agent { } ); } + + private runServices(): void { + // VSCode service + const vscodeToken = generatePassword(64); + this.serviceJournal.set('vscode_token', vscodeToken); + this.services.vsCode.setArgs({ + token: vscodeToken, + port: 8443, + host: '0.0.0.0', + }); + this.services.vsCode.start(); + } } diff --git a/agent/src/lib/services/baseService.ts b/agent/src/lib/services/baseService.ts new file mode 100644 index 0000000..b1671b6 --- /dev/null +++ b/agent/src/lib/services/baseService.ts @@ -0,0 +1,57 @@ +import { ChildProcess, execFile } from 'child_process'; +import { delay } from '../utils'; + +export type ServiceStatus = 'running' | 'stopped'; + +export default abstract class Service { + protected process: ChildProcess; + + constructor( + public name: string, + public execFile: string, + public args: T = null + ) { + this.name = name; + this.execFile = execFile; + this.args = args; + } + + public start(): void { + if (!this.args) { + throw new Error( + `Service ${this.name} has not initialized args, cannot start it` + ); + } + + this.process = execFile(this.execFile, this.argsToShellArgs()); + } + public terminate(): void { + this.process.kill('SIGTERM'); + } + public kill(): void { + this.process.kill('SIGKILL'); + } + public async restart(timeout = 5000): Promise { + this.terminate(); + if (!this.isRunning) { + await delay(timeout); + if (!this.isRunning) { + this.kill(); + } + } + this.start(); + } + public getStatus(): ServiceStatus { + return this.isRunning ? 'running' : 'stopped'; + } + + private get isRunning(): boolean { + return !this.process.killed && this.process.exitCode === null; + } + + public setArgs(newArgs: typeof this.args): void { + this.args = newArgs; + } + + public abstract argsToShellArgs(): string[]; +} diff --git a/agent/src/lib/services/vscode.ts b/agent/src/lib/services/vscode.ts new file mode 100644 index 0000000..a3e006c --- /dev/null +++ b/agent/src/lib/services/vscode.ts @@ -0,0 +1,19 @@ +import { VSCodeConfiguration } from '../typing/types'; +import Service from './baseService'; + +export default class VSCode extends Service { + constructor() { + super('vscode', 'openvscode-server'); + } + + argsToShellArgs(): string[] { + return [ + '--connection-token', + this.args.token, + '--port', + String(this.args.port), + '--host', + this.args.host, + ]; + } +} diff --git a/agent/src/lib/typing/types.ts b/agent/src/lib/typing/types.ts index e88fd69..5850457 100644 --- a/agent/src/lib/typing/types.ts +++ b/agent/src/lib/typing/types.ts @@ -18,3 +18,9 @@ export interface CommandErrorReport extends ErrorReport { } export type MessageRoutingHandler = MessageRouting[keyof MessageRouting]; + +export interface VSCodeConfiguration { + token: string; + port: number; + host: string; +} diff --git a/agent/src/lib/utils.ts b/agent/src/lib/utils.ts index abc44df..2290e80 100644 --- a/agent/src/lib/utils.ts +++ b/agent/src/lib/utils.ts @@ -109,8 +109,9 @@ export function printFatal(message): void { export function getSocketID(socket: WebSocket) { /* eslint-disable */ - return `${(socket)._socket.remoteAddress}:${(socket)._socket.remotePort - }`; + return `${(socket)._socket.remoteAddress}:${ + (socket)._socket.remotePort + }`; /* eslint-disable */ } @@ -120,3 +121,5 @@ export function checkIfSocketClosed(socket: WebSocket): boolean { socket.readyState === WebSocket.CLOSING ); } + +export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/agent/src/ws/MessageRouting.ts b/agent/src/ws/MessageRouting.ts index 547c700..fbcda65 100644 --- a/agent/src/ws/MessageRouting.ts +++ b/agent/src/ws/MessageRouting.ts @@ -6,9 +6,6 @@ import { Task } from '../lib/typing/Task'; import { execAsync, getLastLines } from '../lib/utils'; import { ActualExecException, ExecReportError } from '../lib/typing/errors'; import Ticket from '../lib/typing/Ticket'; -import { readFileSync } from 'fs'; -import { parse } from 'yaml'; -import { homedir } from 'os'; export default class MessageRouting { [key: string]: (agent: Agent, task: Task) => Promise; @@ -131,50 +128,15 @@ export default class MessageRouting { agent: Agent, task: Task ): Promise { - try { - const codeServerConfigPath = - '~/.config/code-server/config.yaml'.replace('~', homedir); - const codeServerConfigContent = readFileSync( - codeServerConfigPath, - 'utf8' - ); - const codeServerConfig: { - 'bind-addr': string; - auth: string; - password: string; - cert: false; - } = parse(codeServerConfigContent); - - if (!codeServerConfig.password) { - throw new Error('No password found in config'); - } - - const clientCommand: MessageDataResponse = new MessageDataResponse({ - taskid: task.id, - action: 'get_vscode_password', - args: { - status: 'success', - password: codeServerConfig.password, - }, - }); - - agent.sendData(clientCommand); - } catch (err: unknown) { - if (err instanceof Error) { - const clientCommand = new MessageDataResponse({ - taskid: task.id, - action: 'get_vscode_password', - args: { - status: 'error', - errorReport: { - message: err.message, - stack: err.stack, - }, - }, - }); - - agent.sendData(clientCommand); - } - } + const clientCommand: MessageDataResponse = new MessageDataResponse({ + taskid: task.id, + action: 'get_vscode_password', + args: { + status: 'success', + password: agent.serviceJournal.get('vscode_token'), + }, + }); + + agent.sendData(clientCommand); } } From 34a47eb31c5c33f794d057762f71c46200b17e3b Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Wed, 17 Aug 2022 22:40:34 +0000 Subject: [PATCH 02/23] Moved dependency to dev dependencies --- daemon/package-lock.json | 22 +++++++++++++--------- daemon/package.json | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/daemon/package-lock.json b/daemon/package-lock.json index cc88bf8..679b4e8 100644 --- a/daemon/package-lock.json +++ b/daemon/package-lock.json @@ -10,7 +10,6 @@ "license": "ISC", "dependencies": { "@hostarteam/mysqlclient": "^1.0.6", - "@types/http-proxy": "^1.17.8", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "express": "^4.17.2", @@ -27,6 +26,7 @@ "@types/bcryptjs": "^2.4.2", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/http-proxy": "^1.17.9", "@types/node": "^17.0.7", "@types/node-fetch": "^2.5.12", "@types/ws": "^8.2.2", @@ -293,9 +293,10 @@ } }, "node_modules/@types/http-proxy": { - "version": "1.17.8", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", - "integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -315,7 +316,8 @@ "node_modules/@types/node": { "version": "17.0.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz", - "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==" + "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==", + "dev": true }, "node_modules/@types/node-fetch": { "version": "2.5.12", @@ -3352,9 +3354,10 @@ } }, "@types/http-proxy": { - "version": "1.17.8", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", - "integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, "requires": { "@types/node": "*" } @@ -3374,7 +3377,8 @@ "@types/node": { "version": "17.0.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz", - "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==" + "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==", + "dev": true }, "@types/node-fetch": { "version": "2.5.12", diff --git a/daemon/package.json b/daemon/package.json index 525f259..c6b45ae 100644 --- a/daemon/package.json +++ b/daemon/package.json @@ -23,7 +23,6 @@ "homepage": "https://github.com/ubarilan/container-workspaces#readme", "dependencies": { "@hostarteam/mysqlclient": "^1.0.6", - "@types/http-proxy": "^1.17.8", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "express": "^4.17.2", @@ -37,6 +36,7 @@ "@types/bcryptjs": "^2.4.2", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/http-proxy": "^1.17.9", "@types/node": "^17.0.7", "@types/node-fetch": "^2.5.12", "@types/ws": "^8.2.2", From 249cd787715d82b2991e046d1d88b076591ab77a Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Wed, 17 Aug 2022 22:41:12 +0000 Subject: [PATCH 03/23] Changed untruthful comment --- daemon/src/lib/routers/container.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/src/lib/routers/container.ts b/daemon/src/lib/routers/container.ts index c50fc51..2bfc3a6 100644 --- a/daemon/src/lib/routers/container.ts +++ b/daemon/src/lib/routers/container.ts @@ -298,7 +298,7 @@ export function initContainerRouter(this: ContainerWorkspaces): void { /** * @param {string} containerID - * This route is used in order to get the last 100 lines of a contrainer's agent logs. + * This route is used in order to get the password from openvscode-server. */ router.get( '/:containerID/vscode/password', From eb744793c5535f9a5ce483dc5e924831bac65097 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Wed, 17 Aug 2022 22:41:51 +0000 Subject: [PATCH 04/23] Added proxy configuration to configuration interface --- daemon/src/lib/typing/types.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/daemon/src/lib/typing/types.ts b/daemon/src/lib/typing/types.ts index b7daa22..c12247c 100644 --- a/daemon/src/lib/typing/types.ts +++ b/daemon/src/lib/typing/types.ts @@ -15,6 +15,7 @@ export interface Configuration { key: string; }; protocol: 'http' | 'https'; + proxy: ProxyConfiguration; database: { host: string; port: number; @@ -30,6 +31,19 @@ export interface Configuration { }; } +export interface ProxyConfiguration { + listenAddress: string; + listenPort: number; + remoteAddress: string; + remotePort: number; + wildcardBaseAddress: string; + protocol: 'http' | 'https'; + sslOptions?: { + cert: string; + key: string; + }; +} + export type httpProtocol = 'http' | 'https'; /** From f6b5a25e7b19973ececa438342c80490ad740c71 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Wed, 17 Aug 2022 22:45:51 +0000 Subject: [PATCH 05/23] Changed permissions on ContainerWorkspaces class in order for proxy to access the required properties and methods --- daemon/src/ContainerWorkspaces.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/daemon/src/ContainerWorkspaces.ts b/daemon/src/ContainerWorkspaces.ts index 51d1d09..12a7b05 100644 --- a/daemon/src/ContainerWorkspaces.ts +++ b/daemon/src/ContainerWorkspaces.ts @@ -39,6 +39,7 @@ export default class ContainerWorkspaces { public readonly remotePort: number; public readonly protocol: Configuration['protocol']; public readonly sslOptions: Configuration['sslOptions']; + public readonly proxyConfig: Configuration['proxy']; private log4js: Log4js; public mainLogger: Logger; @@ -69,7 +70,7 @@ export default class ContainerWorkspaces { protected containerRouter: Router; protected configRouter: Router; protected webSockerRouter; - protected proxmoxClient: ProxmoxConnection; + public proxmoxClient: ProxmoxConnection; protected mySQLClient: MySQLClient; constructor({ @@ -355,14 +356,14 @@ export default class ContainerWorkspaces { /** * Get the status of a container - * @protected + * @public * @method * @async * @param {number} containerID * @param {string} ip * @returns {Promise} */ - protected async getVSCodePassword( + public async getVSCodePassword( containerID: number, ip: string ): Promise { @@ -387,12 +388,12 @@ export default class ContainerWorkspaces { /** * Get a client from the connected clients list - * @protected + * @public * @method * @param {string} ip * @returns {WebSocket | null} */ - protected getConnectedClient(ip: string): WebSocket | null { + public getConnectedClient(ip: string): WebSocket | null { const clientList: WebSocket[] = Array.from(this.wss.clients); const selectedClient: WebSocket = clientList.find( /*eslint-disable */ From 98c313925674c83bbaa5265887bb7879e24720cf Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Wed, 17 Aug 2022 22:55:11 +0000 Subject: [PATCH 06/23] Added base proxy infrastructure --- daemon/src/ContainerWorkspaces.ts | 7 + daemon/src/lib/proxy-manager/ProxyManager.ts | 125 ++++++++++++++++++ .../lib/proxy-manager/common/serviceToPort.ts | 3 + daemon/src/lib/proxy-manager/common/types.ts | 4 + daemon/src/lib/proxy-manager/common/utils.ts | 12 ++ .../proxy-manager/common/validateProxyHost.ts | 35 +++++ daemon/src/lib/proxy-manager/httpHandler.ts | 13 ++ .../lib/proxy-manager/proxies/BaseProxy.ts | 29 ++++ .../src/lib/proxy-manager/proxies/vscode.ts | 54 ++++++++ daemon/src/lib/proxy-manager/proxyHandler.ts | 50 +++++++ .../lib/proxy-manager/routers/apiRouter.ts | 39 ++++++ 11 files changed, 371 insertions(+) create mode 100644 daemon/src/lib/proxy-manager/ProxyManager.ts create mode 100644 daemon/src/lib/proxy-manager/common/serviceToPort.ts create mode 100644 daemon/src/lib/proxy-manager/common/types.ts create mode 100644 daemon/src/lib/proxy-manager/common/utils.ts create mode 100644 daemon/src/lib/proxy-manager/common/validateProxyHost.ts create mode 100644 daemon/src/lib/proxy-manager/httpHandler.ts create mode 100644 daemon/src/lib/proxy-manager/proxies/BaseProxy.ts create mode 100644 daemon/src/lib/proxy-manager/proxies/vscode.ts create mode 100644 daemon/src/lib/proxy-manager/proxyHandler.ts create mode 100644 daemon/src/lib/proxy-manager/routers/apiRouter.ts diff --git a/daemon/src/ContainerWorkspaces.ts b/daemon/src/ContainerWorkspaces.ts index 12a7b05..6f6fdcd 100644 --- a/daemon/src/ContainerWorkspaces.ts +++ b/daemon/src/ContainerWorkspaces.ts @@ -30,6 +30,7 @@ import CT from './lib/entities/CT'; import Task from './lib/entities/Task'; import { parse } from 'path'; import { createProxyServer } from 'http-proxy'; +import ProxyManager from './lib/proxy-manager/ProxyManager'; export default class ContainerWorkspaces { protected httpServer: HttpServer | HttpsServer; @@ -72,6 +73,7 @@ export default class ContainerWorkspaces { protected webSockerRouter; public proxmoxClient: ProxmoxConnection; protected mySQLClient: MySQLClient; + protected proxyManager: ProxyManager; constructor({ listenAddress, @@ -82,6 +84,7 @@ export default class ContainerWorkspaces { sslOptions, database: databaseConf, pve: PVEConf, + proxy: proxyConf, }: Configuration) { this.listenAddress = listenAddress; this.listenPort = listenPort; @@ -89,6 +92,8 @@ export default class ContainerWorkspaces { this.remotePort = remotePort; this.protocol = protocol; this.sslOptions = sslOptions; + this.proxyConfig = proxyConf; + this.configureLoggers(); this.setupHttp(); @@ -106,6 +111,8 @@ export default class ContainerWorkspaces { cw: this, verifyCertificate: false, }); + + this.proxyManager = new ProxyManager(this); } /** diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts new file mode 100644 index 0000000..3882b72 --- /dev/null +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -0,0 +1,125 @@ +import type { Configuration } from '../typing/types'; +import express, { Application, Router } from 'express'; +import { Server as HttpServer, createServer as createHttpServer } from 'http'; +import { + Server as HttpsServer, + createServer as createHttpsServer, +} from 'https'; +import { existsSync, readFileSync } from 'fs'; +import { printError, printSuccess } from '../util/utils'; +import { initializeApiRouter } from './routers/apiRouter'; +import proxyHandler from './proxyHandler'; +import httpHandler from './httpHandler'; +import validateProxy from './common/validateProxyHost'; +import ContainerWorkspaces from '../../ContainerWorkspaces'; +import BaseProxy from './proxies/BaseProxy'; +import VSCodeProxy from './proxies/vscode'; + +export default class ProxyManager { + private httpServer: HttpServer | HttpsServer; + protected readonly config: Configuration['proxy']; + protected webApp: Application; + protected apiRouter: Router; + protected allowedTokens: Map; + protected containerProxyClient: Map; + + protected proxyClients: { + vscode: VSCodeProxy; + }; + + protected httpHandler = httpHandler; + protected proxyHandler = proxyHandler; + + protected validateProxy = validateProxy; + + private initializeApiRouter = initializeApiRouter; + + constructor(public cw: ContainerWorkspaces) { + this.config = cw.proxyConfig; + this.allowedTokens = new Map(); + this.containerProxyClient = new Map(); + + this.startWebServer(); + } + + private startWebServer(): void { + if (this.config.protocol === 'http') { + this.httpServer = createHttpServer(this.webApp); + } else if (this.config.protocol === 'https') { + if (!existsSync(this.config.sslOptions.key)) { + printError( + `The key file '${this.config.sslOptions.key}' does not exist.` + ); + process.exit(1); + } else if (!existsSync(this.config.sslOptions.cert)) { + printError( + `The cert file '${this.config.sslOptions.cert}' does not exist.` + ); + process.exit(1); + } + this.httpServer = createHttpsServer({ + key: readFileSync(this.config.sslOptions.key, { + encoding: 'utf-8', + }), + cert: readFileSync(this.config.sslOptions.cert, { + encoding: 'utf-8', + }), + }); + } else { + printError(`Unknown protocol '${this.config.protocol}'`); + process.exit(1); + } + + this.initRouters(); + + this.webApp = express(); + + this.webApp.disable('x-powered-by'); + this.webApp.use(express.json()); + this.webApp.use(express.urlencoded({ extended: true })); + + this.webApp.use('/pm/api', this.apiRouter.bind(this)); + + this.httpServer.on('request', this.webApp); + + this.httpServer.listen(this.config.listenPort, () => { + printSuccess( + `Proxy Manager server listening on ${this.config.protocol}://${this.config.remoteAddress}:${this.config.remoteAddress}` + ); + }); + } + + private initRouters(): void { + this.initializeApiRouter(); + } + + /** + * Method will add a container to the service proxy manager. + * @param {string} containerID - The container ID to proxy to. + * @param {string} containerIP - The container IP to proxy to. + */ + public addContainer(containerID: number, containerIP: string): void { + containerID; + containerIP; + throw new Error('Method not implemented.'); + } + + /** + * Method will remove a container from the service proxy manager. + * @param {string} containerID - The container ID to remove. + */ + public removeContainer(containerID: number): void { + containerID; + throw new Error('Method not implemented.'); + } + + protected addContainerAccess(containerID: number, token: string): void { + this.allowedTokens.set(token, containerID); + } + + protected removeContainerAccess(token: string): void { + if (this.allowedTokens.has(token)) { + this.allowedTokens.delete(token); + } + } +} diff --git a/daemon/src/lib/proxy-manager/common/serviceToPort.ts b/daemon/src/lib/proxy-manager/common/serviceToPort.ts new file mode 100644 index 0000000..d110595 --- /dev/null +++ b/daemon/src/lib/proxy-manager/common/serviceToPort.ts @@ -0,0 +1,3 @@ +export default { + vscode: 8443, +} as Record; diff --git a/daemon/src/lib/proxy-manager/common/types.ts b/daemon/src/lib/proxy-manager/common/types.ts new file mode 100644 index 0000000..f06633e --- /dev/null +++ b/daemon/src/lib/proxy-manager/common/types.ts @@ -0,0 +1,4 @@ +export interface ProxyInfo { + service: string; + containerID: number; +} diff --git a/daemon/src/lib/proxy-manager/common/utils.ts b/daemon/src/lib/proxy-manager/common/utils.ts new file mode 100644 index 0000000..756999a --- /dev/null +++ b/daemon/src/lib/proxy-manager/common/utils.ts @@ -0,0 +1,12 @@ +import { ProxyInfo } from './types'; + +export function getProxyInfo(hostname: string): ProxyInfo | null { + const dataSection = hostname.split('.')[0]; + if (dataSection.split('-').length < 2) return null; + + const [service, containerIDString] = dataSection.split('-'); + const containerID = Number(containerIDString); + if (isNaN(containerID)) return null; + + return { service, containerID }; +} diff --git a/daemon/src/lib/proxy-manager/common/validateProxyHost.ts b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts new file mode 100644 index 0000000..58d306a --- /dev/null +++ b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts @@ -0,0 +1,35 @@ +import type { Request, Response } from 'express'; +import ProxyManager from '../ProxyManager'; +import { getProxyInfo } from './utils'; +import serviceToPort from './serviceToPort'; + +export default function validateProxy( + this: ProxyManager, + req: Request, + res: Response +) { + if (req.hostname === this.config.remoteAddress) { + res.status(500).send({ + message: 'Request on PM base address was routed to proxy handler', + }); + return false; + } + + const proxyInfo = getProxyInfo(req.hostname); + if (!proxyInfo) { + res.status(500).send({ + message: 'Request on invalid host was routed to proxy handler', + }); + return false; + } + + const servicePort = serviceToPort[proxyInfo.service]; + if (!servicePort) { + res.status(500).send({ + message: 'Invalid service specified on hostname', + }); + return false; + } + + return true; +} diff --git a/daemon/src/lib/proxy-manager/httpHandler.ts b/daemon/src/lib/proxy-manager/httpHandler.ts new file mode 100644 index 0000000..5cadeac --- /dev/null +++ b/daemon/src/lib/proxy-manager/httpHandler.ts @@ -0,0 +1,13 @@ +import type { NextFunction, Request, Response } from 'express'; +import ProxyManager from './ProxyManager'; + +export default function httpHandler( + this: ProxyManager, + req: Request, + res: Response, + next: NextFunction +): void { + if (req.hostname === this.config.remoteAddress) { + return next('router'); + } +} diff --git a/daemon/src/lib/proxy-manager/proxies/BaseProxy.ts b/daemon/src/lib/proxy-manager/proxies/BaseProxy.ts new file mode 100644 index 0000000..ab31f5a --- /dev/null +++ b/daemon/src/lib/proxy-manager/proxies/BaseProxy.ts @@ -0,0 +1,29 @@ +import type { Request, Response } from 'express'; +import ProxyManager from '../ProxyManager'; + +export interface ProxyOptions { + host: string; + port: number; + containerID: number; +} + +export default abstract class BaseProxy< + T extends ProxyOptions | unknown = unknown, + U = unknown +> { + public abstract readonly authRequired: boolean; + protected auth: U; + + constructor(protected config: T, protected pm: ProxyManager) {} + + public abstract handleFetch(req: Request, res: Response): Promise; + + public initWebSocket(url: string): void { + url; + throw new Error('Method not implemented.'); + } + + public fetchAuth(): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/daemon/src/lib/proxy-manager/proxies/vscode.ts b/daemon/src/lib/proxy-manager/proxies/vscode.ts new file mode 100644 index 0000000..a86d21d --- /dev/null +++ b/daemon/src/lib/proxy-manager/proxies/vscode.ts @@ -0,0 +1,54 @@ +import { Request, Response } from 'express'; +import fetch from 'node-fetch'; +import ProxyManager from '../ProxyManager'; +import BaseProxy, { ProxyOptions } from './BaseProxy'; + +export default class VSCodeProxy extends BaseProxy { + public authRequired = true; + constructor(config: ProxyOptions, pm: ProxyManager) { + super(config, pm); + // this.initWebSocket(`ws://${this.config.host}:${this.config.port}/ws`); + } + + public async handleFetch(req: Request, res: Response): Promise { + try { + const proxyRes = await fetch( + `http://${this.config.host}:${this.config.port}${req.url}`, + { + headers: { + ...Object(req.headers), + accept: '*/*', + 'upgrade-insecure-requests': '1', + Cookie: `vscode-tkn=${this.auth}`, + Host: `${req.agentIP}:${this.config.port}`, + }, + method: req.method, + ...(Object.keys(req.body).length > 0 && { body: req.body }), + follow: 0, + } + ); + + const text = await proxyRes.text(); + + res.set(Object.fromEntries(proxyRes.headers.entries())); + res.removeHeader('Set-Cookie'); + res.status(proxyRes.status); + + res.send(text); + } catch (err: unknown) { + if (err instanceof Error) { + res.status(500).send({ + status: 'error', + message: `Error occurred while proxying request to VSCode server - ${err.message}`, + }); + } + } + } + + public async fetchAuth(): Promise { + this.auth = await this.pm.cw.getVSCodePassword( + this.config.containerID, + this.config.host + ); + } +} diff --git a/daemon/src/lib/proxy-manager/proxyHandler.ts b/daemon/src/lib/proxy-manager/proxyHandler.ts new file mode 100644 index 0000000..e59ad9b --- /dev/null +++ b/daemon/src/lib/proxy-manager/proxyHandler.ts @@ -0,0 +1,50 @@ +import type { Request, Response } from 'express'; +import ProxyManager from './ProxyManager'; +import { getProxyInfo } from './common/utils'; +import serviceToPort from './common/serviceToPort'; + +export default async function proxyHandler( + this: ProxyManager, + req: Request, + res: Response +) { + const valid = this.validateProxy(req, res); + if (!valid) return; + + const proxyInfo = getProxyInfo(req.hostname); + + const targetAddress = await this.cw.proxmoxClient.getContainerIP( + proxyInfo.containerID + ); + + if (!targetAddress) { + return res.status(404).send({ + message: 'No such container', + }); + } + + const targetConnected = await this.cw.getConnectedClient(targetAddress); + + if (!targetConnected) { + res.status(409).send({ + status: 'conflict', + message: 'Container with given ID is not connected to this server', + }); + } + + let proxyClient = this.containerProxyClient.get(proxyInfo.containerID); + if (!proxyClient) { + proxyClient = new this.proxyClients[proxyInfo.service]({ + host: targetAddress, + port: serviceToPort[proxyInfo.service], + containerID: proxyInfo.containerID, + }); + if (proxyClient.authRequired) { + await proxyClient.fetchAuth(); + } + + this.containerProxyClient.set(proxyInfo.containerID, proxyClient); + } + + proxyClient.handleFetch(req, res); +} diff --git a/daemon/src/lib/proxy-manager/routers/apiRouter.ts b/daemon/src/lib/proxy-manager/routers/apiRouter.ts new file mode 100644 index 0000000..3bb3848 --- /dev/null +++ b/daemon/src/lib/proxy-manager/routers/apiRouter.ts @@ -0,0 +1,39 @@ +import ProxyManager from '../ProxyManager'; +import { Router, Request, Response } from 'express'; + +export function initializeApiRouter(this: ProxyManager) { + const router: Router = Router(); + this.apiRouter = router; + + router.post('/addcontaineraccess', (req: Request, res: Response) => { + const { + token, + containerID, + }: { + token: string; + containerID: string; + } = req.body; + + const containerIDNumber = parseInt(containerID); + if (isNaN(containerIDNumber)) { + res.status(400).send('Invalid container ID'); + return; + } + + this.addContainerAccess(containerIDNumber, token); + + res.send({ status: 'ok' }); + }); + + router.delete('/removecontaineraccess', (req: Request, res: Response) => { + const { + token, + }: { + token: string; + } = req.body; + + this.removeContainerAccess(token); + + res.send({ status: 'ok' }); + }); +} From 43f4ba8b2c8eb7db8e33409d103d7d2ad7d5ad2d Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Wed, 17 Aug 2022 22:56:46 +0000 Subject: [PATCH 07/23] Fixed logging mistake --- daemon/src/lib/proxy-manager/ProxyManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index 3882b72..2525a0b 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -84,7 +84,7 @@ export default class ProxyManager { this.httpServer.listen(this.config.listenPort, () => { printSuccess( - `Proxy Manager server listening on ${this.config.protocol}://${this.config.remoteAddress}:${this.config.remoteAddress}` + `Proxy Manager server listening on ${this.config.protocol}://${this.config.remoteAddress}:${this.config.remotePort}` ); }); } From 1b65f466483f5bad61934a89c51e5118aaf1895c Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Wed, 17 Aug 2022 23:22:23 +0000 Subject: [PATCH 08/23] Fixed a few bugs and can now fetch binary files too --- daemon/src/lib/proxy-manager/ProxyManager.ts | 6 ++++-- daemon/src/lib/proxy-manager/httpHandler.ts | 2 ++ daemon/src/lib/proxy-manager/proxies/vscode.ts | 9 +++++++-- daemon/src/lib/proxy-manager/proxyHandler.ts | 13 ++++++++----- daemon/src/lib/proxy-manager/routers/apiRouter.ts | 6 ++++++ 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index 2525a0b..6036978 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -23,8 +23,8 @@ export default class ProxyManager { protected allowedTokens: Map; protected containerProxyClient: Map; - protected proxyClients: { - vscode: VSCodeProxy; + protected proxyClients = { + vscode: VSCodeProxy, }; protected httpHandler = httpHandler; @@ -78,6 +78,8 @@ export default class ProxyManager { this.webApp.use(express.json()); this.webApp.use(express.urlencoded({ extended: true })); + this.webApp.use(this.httpHandler.bind(this)); + this.webApp.use('/pm/api', this.apiRouter.bind(this)); this.httpServer.on('request', this.webApp); diff --git a/daemon/src/lib/proxy-manager/httpHandler.ts b/daemon/src/lib/proxy-manager/httpHandler.ts index 5cadeac..82933a9 100644 --- a/daemon/src/lib/proxy-manager/httpHandler.ts +++ b/daemon/src/lib/proxy-manager/httpHandler.ts @@ -10,4 +10,6 @@ export default function httpHandler( if (req.hostname === this.config.remoteAddress) { return next('router'); } + + this.proxyHandler(req, res); } diff --git a/daemon/src/lib/proxy-manager/proxies/vscode.ts b/daemon/src/lib/proxy-manager/proxies/vscode.ts index a86d21d..2fde09e 100644 --- a/daemon/src/lib/proxy-manager/proxies/vscode.ts +++ b/daemon/src/lib/proxy-manager/proxies/vscode.ts @@ -28,13 +28,18 @@ export default class VSCodeProxy extends BaseProxy { } ); - const text = await proxyRes.text(); + let resp: unknown; + if (proxyRes.headers.get('content-type').startsWith('image/')) { + resp = await proxyRes.buffer(); + } else { + resp = await proxyRes.text(); + } res.set(Object.fromEntries(proxyRes.headers.entries())); res.removeHeader('Set-Cookie'); res.status(proxyRes.status); - res.send(text); + res.send(resp); } catch (err: unknown) { if (err instanceof Error) { res.status(500).send({ diff --git a/daemon/src/lib/proxy-manager/proxyHandler.ts b/daemon/src/lib/proxy-manager/proxyHandler.ts index e59ad9b..ee490fe 100644 --- a/daemon/src/lib/proxy-manager/proxyHandler.ts +++ b/daemon/src/lib/proxy-manager/proxyHandler.ts @@ -34,11 +34,14 @@ export default async function proxyHandler( let proxyClient = this.containerProxyClient.get(proxyInfo.containerID); if (!proxyClient) { - proxyClient = new this.proxyClients[proxyInfo.service]({ - host: targetAddress, - port: serviceToPort[proxyInfo.service], - containerID: proxyInfo.containerID, - }); + proxyClient = new this.proxyClients[proxyInfo.service]( + { + host: targetAddress, + port: serviceToPort[proxyInfo.service], + containerID: proxyInfo.containerID, + }, + this + ); if (proxyClient.authRequired) { await proxyClient.fetchAuth(); } diff --git a/daemon/src/lib/proxy-manager/routers/apiRouter.ts b/daemon/src/lib/proxy-manager/routers/apiRouter.ts index 3bb3848..c4055be 100644 --- a/daemon/src/lib/proxy-manager/routers/apiRouter.ts +++ b/daemon/src/lib/proxy-manager/routers/apiRouter.ts @@ -5,6 +5,12 @@ export function initializeApiRouter(this: ProxyManager) { const router: Router = Router(); this.apiRouter = router; + router.all('*', (req, res, next) => { + if (req.hostname !== this.config.remoteAddress) { + return next('router'); + } + }); + router.post('/addcontaineraccess', (req: Request, res: Response) => { const { token, From 6b2aa77395760e4bb8a83306179b892a00894bd7 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Thu, 18 Aug 2022 09:39:18 +0000 Subject: [PATCH 09/23] Fixed issue with buffer proxy responses --- .../src/lib/proxy-manager/proxies/vscode.ts | 30 +++++++++---------- daemon/src/lib/proxy-manager/proxyHandler.ts | 2 +- daemon/src/lib/util/utils.ts | 9 ++++++ 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/daemon/src/lib/proxy-manager/proxies/vscode.ts b/daemon/src/lib/proxy-manager/proxies/vscode.ts index 2fde09e..51a5601 100644 --- a/daemon/src/lib/proxy-manager/proxies/vscode.ts +++ b/daemon/src/lib/proxy-manager/proxies/vscode.ts @@ -1,5 +1,6 @@ import { Request, Response } from 'express'; import fetch from 'node-fetch'; +import { delay } from '../../util/utils'; import ProxyManager from '../ProxyManager'; import BaseProxy, { ProxyOptions } from './BaseProxy'; @@ -28,20 +29,12 @@ export default class VSCodeProxy extends BaseProxy { } ); - let resp: unknown; - if (proxyRes.headers.get('content-type').startsWith('image/')) { - resp = await proxyRes.buffer(); - } else { - resp = await proxyRes.text(); - } - res.set(Object.fromEntries(proxyRes.headers.entries())); res.removeHeader('Set-Cookie'); res.status(proxyRes.status); - - res.send(resp); + proxyRes.body.pipe(res); } catch (err: unknown) { - if (err instanceof Error) { + if (err instanceof Error && !res.headersSent) { res.status(500).send({ status: 'error', message: `Error occurred while proxying request to VSCode server - ${err.message}`, @@ -50,10 +43,17 @@ export default class VSCodeProxy extends BaseProxy { } } - public async fetchAuth(): Promise { - this.auth = await this.pm.cw.getVSCodePassword( - this.config.containerID, - this.config.host - ); + public async fetchAuth(retry = 0, maxRetry = 5): Promise { + try { + this.auth = await this.pm.cw.getVSCodePassword( + this.config.containerID, + this.config.host + ); + } catch (err) { + if (retry < maxRetry) { + await delay(1000); + await this.fetchAuth(retry + 1, maxRetry); + } + } } } diff --git a/daemon/src/lib/proxy-manager/proxyHandler.ts b/daemon/src/lib/proxy-manager/proxyHandler.ts index ee490fe..ceb0d87 100644 --- a/daemon/src/lib/proxy-manager/proxyHandler.ts +++ b/daemon/src/lib/proxy-manager/proxyHandler.ts @@ -23,7 +23,7 @@ export default async function proxyHandler( }); } - const targetConnected = await this.cw.getConnectedClient(targetAddress); + const targetConnected = this.cw.getConnectedClient(targetAddress); if (!targetConnected) { res.status(409).send({ diff --git a/daemon/src/lib/util/utils.ts b/daemon/src/lib/util/utils.ts index 2964784..3e39047 100644 --- a/daemon/src/lib/util/utils.ts +++ b/daemon/src/lib/util/utils.ts @@ -258,3 +258,12 @@ export async function checkAuthToken( return compareSync(client_secret, result.client_secret); } + +/** + * Wait for a specified amount of time + * @param {number} ms + * @returns {Promise} + * @async + */ +export const delay = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); From b7abdadfd3471ae47a92084dcf83ad6b808f3d65 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Sat, 20 Aug 2022 14:55:18 +0000 Subject: [PATCH 10/23] Fixed null value when fetching /api/container/status route --- daemon/src/lib/proxmox/apiTools.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daemon/src/lib/proxmox/apiTools.ts b/daemon/src/lib/proxmox/apiTools.ts index d015dd8..6661b04 100644 --- a/daemon/src/lib/proxmox/apiTools.ts +++ b/daemon/src/lib/proxmox/apiTools.ts @@ -835,7 +835,9 @@ export async function getContainerStatuses( const containerData = this.getContainerStatus(ct.id); if (containerData) containerStatusesAsPromises.push(containerData); } - return await Promise.all(containerStatusesAsPromises); + return (await Promise.all(containerStatusesAsPromises)).filter( + (container) => !!container + ); } export async function getStorages( From ba3ad7f2b6a83b444f755cfc224bf3de474b4913 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Mon, 22 Aug 2022 20:03:28 +0000 Subject: [PATCH 11/23] Fixed webshell --- agent/src/WebShell/WebShell.ts | 2 +- daemon/src/ContainerWorkspaces.ts | 8 +++++++- daemon/src/lib/routers/container.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/agent/src/WebShell/WebShell.ts b/agent/src/WebShell/WebShell.ts index ea4fedc..dc09595 100644 --- a/agent/src/WebShell/WebShell.ts +++ b/agent/src/WebShell/WebShell.ts @@ -50,7 +50,7 @@ export default class WebShell { socket: WebSocket, request: IncomingMessage ): void { - const params = new URLSearchParams(request.url); + const params = new URL(`http://example.com${request.url}`).searchParams; if (!params) return socket.close(1011, 'Invalid params'); const ticketID: string = params.get('ticket'); if (!ticketID) return socket.close(1011, 'No ticket'); diff --git a/daemon/src/ContainerWorkspaces.ts b/daemon/src/ContainerWorkspaces.ts index 6f6fdcd..9ecfecb 100644 --- a/daemon/src/ContainerWorkspaces.ts +++ b/daemon/src/ContainerWorkspaces.ts @@ -174,7 +174,13 @@ export default class ContainerWorkspaces { }, ws: true, }); - proxy.ws(request, socket, head); + try { + proxy.ws(request, socket, head, { + proxyTimeout: 5000, + }); + } catch (e) { + this.wsLogger.error(`Got error while proxying: ${e}`); + } } else request.destroy(); }); } diff --git a/daemon/src/lib/routers/container.ts b/daemon/src/lib/routers/container.ts index 2bfc3a6..8ef7b4f 100644 --- a/daemon/src/lib/routers/container.ts +++ b/daemon/src/lib/routers/container.ts @@ -452,7 +452,7 @@ export function initContainerRouter(this: ContainerWorkspaces): void { const ticketID = generateID(); const ticket: Ticket = { id: ticketID, - expires: Date.now() + 1000 * 60, // One minute + expires: Date.now() + 1000 * 60 * 60 * 4, // 4 Hours }; try { From 1b784b69278e449b79ba234935fbdfc7c4f08713 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Sun, 28 Aug 2022 12:03:35 +0000 Subject: [PATCH 12/23] Fixed daemon crashing while agent restarts bug Co-authored-by: Idan Carlebach --- daemon/src/ContainerWorkspaces.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/daemon/src/ContainerWorkspaces.ts b/daemon/src/ContainerWorkspaces.ts index 9ecfecb..d13f124 100644 --- a/daemon/src/ContainerWorkspaces.ts +++ b/daemon/src/ContainerWorkspaces.ts @@ -175,9 +175,15 @@ export default class ContainerWorkspaces { ws: true, }); try { - proxy.ws(request, socket, head, { - proxyTimeout: 5000, - }); + proxy.ws( + request, + socket, + head, + { + proxyTimeout: 5000, + }, + () => null + ); } catch (e) { this.wsLogger.error(`Got error while proxying: ${e}`); } From 0ef09d92bc6da9cbac926669e259a9655ed40a6c Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Sun, 28 Aug 2022 12:05:28 +0000 Subject: [PATCH 13/23] Added important TODO comments Co-authored-by: Idan Carlebach --- daemon/src/lib/proxy-manager/httpHandler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daemon/src/lib/proxy-manager/httpHandler.ts b/daemon/src/lib/proxy-manager/httpHandler.ts index 82933a9..cca7807 100644 --- a/daemon/src/lib/proxy-manager/httpHandler.ts +++ b/daemon/src/lib/proxy-manager/httpHandler.ts @@ -7,9 +7,11 @@ export default function httpHandler( res: Response, next: NextFunction ): void { + // TODO: Handle API auth if (req.hostname === this.config.remoteAddress) { return next('router'); } + // TODO: Handle proxy auth this.proxyHandler(req, res); } From 4dcf97525c3035107854b0a4a034b130c57fc8ba Mon Sep 17 00:00:00 2001 From: Idan Carlebach Date: Sun, 28 Aug 2022 12:07:59 +0000 Subject: [PATCH 14/23] Made vscode websocket proxy work Co-authored-by: Uri Bar Ilan --- daemon/src/lib/proxy-manager/ProxyManager.ts | 68 ++++++++++++++++++- .../proxy-manager/common/validateProxyHost.ts | 28 ++++++++ .../lib/proxy-manager/proxies/BaseProxy.ts | 13 ++++ .../src/lib/proxy-manager/proxies/vscode.ts | 23 ++++++- 4 files changed, 127 insertions(+), 5 deletions(-) diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index 6036978..263eac9 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -1,6 +1,10 @@ import type { Configuration } from '../typing/types'; import express, { Application, Router } from 'express'; -import { Server as HttpServer, createServer as createHttpServer } from 'http'; +import { + Server as HttpServer, + createServer as createHttpServer, + IncomingMessage, +} from 'http'; import { Server as HttpsServer, createServer as createHttpsServer, @@ -10,13 +14,18 @@ import { printError, printSuccess } from '../util/utils'; import { initializeApiRouter } from './routers/apiRouter'; import proxyHandler from './proxyHandler'; import httpHandler from './httpHandler'; -import validateProxy from './common/validateProxyHost'; +import validateProxy, { validateWSProxy } from './common/validateProxyHost'; import ContainerWorkspaces from '../../ContainerWorkspaces'; import BaseProxy from './proxies/BaseProxy'; import VSCodeProxy from './proxies/vscode'; +import { WebSocketServer } from 'ws'; +import { getProxyInfo } from './common/utils'; +import serviceToPort from './common/serviceToPort'; +import { Duplex } from 'stream'; export default class ProxyManager { private httpServer: HttpServer | HttpsServer; + private wss: WebSocketServer; protected readonly config: Configuration['proxy']; protected webApp: Application; protected apiRouter: Router; @@ -31,6 +40,7 @@ export default class ProxyManager { protected proxyHandler = proxyHandler; protected validateProxy = validateProxy; + protected validateWSProxy = validateWSProxy; private initializeApiRouter = initializeApiRouter; @@ -83,6 +93,7 @@ export default class ProxyManager { this.webApp.use('/pm/api', this.apiRouter.bind(this)); this.httpServer.on('request', this.webApp); + this.initWebSocketServer(); this.httpServer.listen(this.config.listenPort, () => { printSuccess( @@ -95,6 +106,59 @@ export default class ProxyManager { this.initializeApiRouter(); } + private initWebSocketServer(): void { + this.httpServer.on( + 'upgrade', + async (request: IncomingMessage, socket: Duplex, head: Buffer) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((socket as any).servername === this.config.remoteAddress) + return socket.destroy(); + + const proxyInfo = getProxyInfo( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (request.socket as any).servername + ); + + const targetAddress = + await this.cw.proxmoxClient.getContainerIP( + proxyInfo.containerID + ); + + if (!targetAddress) { + return socket.destroy(); + } + + if (!this.validateWSProxy(request, socket)) + return socket.destroy(); + + let proxyClient = this.containerProxyClient.get( + proxyInfo.containerID + ); + if (!proxyClient) { + proxyClient = new this.proxyClients[proxyInfo.service]( + { + host: targetAddress, + port: serviceToPort[proxyInfo.service], + containerID: proxyInfo.containerID, + }, + this + ); + + if (proxyClient.authRequired) { + await proxyClient.fetchAuth(); + } + + this.containerProxyClient.set( + proxyInfo.containerID, + proxyClient + ); + } + + proxyClient.handleHttpUpgrade(request, socket, head); + } + ); + } + /** * Method will add a container to the service proxy manager. * @param {string} containerID - The container ID to proxy to. diff --git a/daemon/src/lib/proxy-manager/common/validateProxyHost.ts b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts index 58d306a..18dd6c4 100644 --- a/daemon/src/lib/proxy-manager/common/validateProxyHost.ts +++ b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts @@ -2,6 +2,9 @@ import type { Request, Response } from 'express'; import ProxyManager from '../ProxyManager'; import { getProxyInfo } from './utils'; import serviceToPort from './serviceToPort'; +import { IncomingMessage } from 'http'; +import WebSocket from 'ws'; +import { Duplex } from 'stream'; export default function validateProxy( this: ProxyManager, @@ -33,3 +36,28 @@ export default function validateProxy( return true; } + +export function validateWSProxy( + this: ProxyManager, + req: IncomingMessage, + socket: Duplex +) { + if (req.headers.host === this.config.remoteAddress) { + socket.destroy(); + return false; + } + + const proxyInfo = getProxyInfo(req.headers.host); + if (!proxyInfo) { + socket.destroy(); + return false; + } + + const servicePort = serviceToPort[proxyInfo.service]; + if (!servicePort) { + socket.destroy(); + return false; + } + + return true; +} diff --git a/daemon/src/lib/proxy-manager/proxies/BaseProxy.ts b/daemon/src/lib/proxy-manager/proxies/BaseProxy.ts index ab31f5a..53ca29d 100644 --- a/daemon/src/lib/proxy-manager/proxies/BaseProxy.ts +++ b/daemon/src/lib/proxy-manager/proxies/BaseProxy.ts @@ -1,4 +1,6 @@ import type { Request, Response } from 'express'; +import { IncomingMessage } from 'http'; +import { Duplex } from 'stream'; import ProxyManager from '../ProxyManager'; export interface ProxyOptions { @@ -26,4 +28,15 @@ export default abstract class BaseProxy< public fetchAuth(): Promise { throw new Error('Method not implemented.'); } + + public handleHttpUpgrade( + request: IncomingMessage, + socket: Duplex, + head: Buffer + ): void { + request; + socket; + head; + throw new Error('Method not implemented.'); + } } diff --git a/daemon/src/lib/proxy-manager/proxies/vscode.ts b/daemon/src/lib/proxy-manager/proxies/vscode.ts index 51a5601..84ece23 100644 --- a/daemon/src/lib/proxy-manager/proxies/vscode.ts +++ b/daemon/src/lib/proxy-manager/proxies/vscode.ts @@ -1,5 +1,8 @@ import { Request, Response } from 'express'; +import { IncomingMessage } from 'http'; +import { createProxy } from 'http-proxy'; import fetch from 'node-fetch'; +import { Duplex } from 'stream'; import { delay } from '../../util/utils'; import ProxyManager from '../ProxyManager'; import BaseProxy, { ProxyOptions } from './BaseProxy'; @@ -20,8 +23,8 @@ export default class VSCodeProxy extends BaseProxy { ...Object(req.headers), accept: '*/*', 'upgrade-insecure-requests': '1', - Cookie: `vscode-tkn=${this.auth}`, - Host: `${req.agentIP}:${this.config.port}`, + cookie: `vscode-tkn=${this.auth}`, + host: `${req.agentIP}:${this.config.port}`, }, method: req.method, ...(Object.keys(req.body).length > 0 && { body: req.body }), @@ -30,7 +33,6 @@ export default class VSCodeProxy extends BaseProxy { ); res.set(Object.fromEntries(proxyRes.headers.entries())); - res.removeHeader('Set-Cookie'); res.status(proxyRes.status); proxyRes.body.pipe(res); } catch (err: unknown) { @@ -56,4 +58,19 @@ export default class VSCodeProxy extends BaseProxy { } } } + + public handleHttpUpgrade( + request: IncomingMessage, + socket: Duplex, + head: Buffer + ): void { + const proxy = createProxy({ + target: `ws://${this.config.host}:${this.config.port}${request.url}`, + headers: { + Cookie: `vscode-tkn=${this.auth}`, + }, + }); + + proxy.ws(request, socket, head, null, () => null); + } } From 2ea4f9b46bf2f614a336e1045d19c305729bb780 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Sun, 28 Aug 2022 12:31:41 +0000 Subject: [PATCH 15/23] Added API authentication to PM Co-authored-by: Idan Carlebach --- daemon/src/ContainerWorkspaces.ts | 2 +- daemon/src/lib/proxy-manager/ProxyManager.ts | 10 ++++++++-- daemon/src/lib/proxy-manager/httpHandler.ts | 1 - 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/daemon/src/ContainerWorkspaces.ts b/daemon/src/ContainerWorkspaces.ts index d13f124..294e4ed 100644 --- a/daemon/src/ContainerWorkspaces.ts +++ b/daemon/src/ContainerWorkspaces.ts @@ -61,7 +61,7 @@ export default class ContainerWorkspaces { protected checkContainerID = checkContainerID; protected sendTaskToAgent = sendTaskToAgent; protected httpLoggerMiddleware = httpLoggerMiddleware; - protected authMiddleware = authMiddleware; + public authMiddleware = authMiddleware; protected checkAuthToken = checkAuthToken; protected webApp: Application; diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index 263eac9..048fef8 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -1,5 +1,5 @@ import type { Configuration } from '../typing/types'; -import express, { Application, Router } from 'express'; +import express, { Application, Handler, Router } from 'express'; import { Server as HttpServer, createServer as createHttpServer, @@ -31,6 +31,7 @@ export default class ProxyManager { protected apiRouter: Router; protected allowedTokens: Map; protected containerProxyClient: Map; + protected authMiddleware: () => Handler; protected proxyClients = { vscode: VSCodeProxy, @@ -46,6 +47,7 @@ export default class ProxyManager { constructor(public cw: ContainerWorkspaces) { this.config = cw.proxyConfig; + this.authMiddleware = cw.authMiddleware; this.allowedTokens = new Map(); this.containerProxyClient = new Map(); @@ -90,7 +92,11 @@ export default class ProxyManager { this.webApp.use(this.httpHandler.bind(this)); - this.webApp.use('/pm/api', this.apiRouter.bind(this)); + this.webApp.use( + '/pm/api', + this.authMiddleware(), + this.apiRouter.bind(this) + ); this.httpServer.on('request', this.webApp); this.initWebSocketServer(); diff --git a/daemon/src/lib/proxy-manager/httpHandler.ts b/daemon/src/lib/proxy-manager/httpHandler.ts index cca7807..66201be 100644 --- a/daemon/src/lib/proxy-manager/httpHandler.ts +++ b/daemon/src/lib/proxy-manager/httpHandler.ts @@ -7,7 +7,6 @@ export default function httpHandler( res: Response, next: NextFunction ): void { - // TODO: Handle API auth if (req.hostname === this.config.remoteAddress) { return next('router'); } From 6808b37c4a05c1bc1696960ff568c0e39458334c Mon Sep 17 00:00:00 2001 From: Idan Carlebach Date: Tue, 30 Aug 2022 17:30:02 +0000 Subject: [PATCH 16/23] Added token creation and deletion via daemon API --- daemon/src/ContainerWorkspaces.ts | 48 +++++++------ daemon/src/http.ts | 1 + daemon/src/lib/proxy-manager/ProxyManager.ts | 31 +++----- daemon/src/lib/proxy-manager/common/types.ts | 4 +- .../proxy-manager/common/validateProxyHost.ts | 1 - .../lib/proxy-manager/routers/apiRouter.ts | 45 ------------ daemon/src/lib/routers/pm.ts | 71 +++++++++++++++++++ 7 files changed, 111 insertions(+), 90 deletions(-) delete mode 100644 daemon/src/lib/proxy-manager/routers/apiRouter.ts create mode 100644 daemon/src/lib/routers/pm.ts diff --git a/daemon/src/ContainerWorkspaces.ts b/daemon/src/ContainerWorkspaces.ts index 294e4ed..0a26794 100644 --- a/daemon/src/ContainerWorkspaces.ts +++ b/daemon/src/ContainerWorkspaces.ts @@ -1,36 +1,37 @@ -import { Application, Router, Request, Response, Handler } from 'express'; +import { ConnectionOptions, MySQLClient } from '@hostarteam/mysqlclient'; +import { Application, Handler, Request, Response, Router } from 'express'; import { Server as HttpServer } from 'http'; +import { createProxyServer } from 'http-proxy'; import { Server as HttpsServer } from 'https'; import { Log4js, Logger } from 'log4js'; -import { initMainRouter } from './lib/routers/main'; +import { parse } from 'path'; +import { WebSocket, WebSocketServer } from 'ws'; +import setupHttp from './http'; +import CT from './lib/entities/CT'; +import Task from './lib/entities/Task'; +import authMiddleware from './lib/middleware/auth'; +import httpLoggerMiddleware from './lib/middleware/logging'; +import ProxmoxConnection from './lib/proxmox/ProxmoxConnection'; +import ProxyManager from './lib/proxy-manager/ProxyManager'; import { initAgentRouter } from './lib/routers/agent'; +import { initConfigRouter } from './lib/routers/config'; import { initContainerRouter } from './lib/routers/container'; +import { initMainRouter } from './lib/routers/main'; +import { initPMRouter } from './lib/routers/pm'; +import { MessageData } from './lib/typing/MessageData'; +import { CTHardwareOptions } from './lib/typing/options'; +import { Config, Configuration, status, Ticket } from './lib/typing/types'; import { checkAuthToken, - checkIP, checkContainerID, + checkIP, createLoggers, printSuccess, sleep, } from './lib/util/utils'; -import ProxmoxConnection from './lib/proxmox/ProxmoxConnection'; -import { WebSocket, WebSocketServer } from 'ws'; -import { handleMessage } from './lib/ws/wsMessageHandler'; -import { wsCommand } from './lib/ws/routing/wsCommand'; import { ClientNotFoundError, sendTaskToAgent } from './lib/ws/commandAgent'; -import { Config, status, Configuration, Ticket } from './lib/typing/types'; -import { MessageData } from './lib/typing/MessageData'; -import { initConfigRouter } from './lib/routers/config'; -import { CTHardwareOptions } from './lib/typing/options'; -import setupHttp from './http'; -import httpLoggerMiddleware from './lib/middleware/logging'; -import authMiddleware from './lib/middleware/auth'; -import { MySQLClient, ConnectionOptions } from '@hostarteam/mysqlclient'; -import CT from './lib/entities/CT'; -import Task from './lib/entities/Task'; -import { parse } from 'path'; -import { createProxyServer } from 'http-proxy'; -import ProxyManager from './lib/proxy-manager/ProxyManager'; +import { wsCommand } from './lib/ws/routing/wsCommand'; +import { handleMessage } from './lib/ws/wsMessageHandler'; export default class ContainerWorkspaces { protected httpServer: HttpServer | HttpsServer; @@ -55,6 +56,7 @@ export default class ContainerWorkspaces { protected initAgentRouter = initAgentRouter; protected initConfigRouter = initConfigRouter; protected initContainerRouter = initContainerRouter; + protected initPMRouter = initPMRouter; protected handleMessage = handleMessage; protected wsCommand = wsCommand; protected checkIP = checkIP; @@ -70,6 +72,7 @@ export default class ContainerWorkspaces { protected agentRouter: Router; protected containerRouter: Router; protected configRouter: Router; + protected pmRouter: Router; protected webSockerRouter; public proxmoxClient: ProxmoxConnection; protected mySQLClient: MySQLClient; @@ -96,6 +99,8 @@ export default class ContainerWorkspaces { this.configureLoggers(); + this.proxyManager = new ProxyManager(this); + this.setupHttp(); this.connectDatabase(databaseConf); @@ -111,8 +116,6 @@ export default class ContainerWorkspaces { cw: this, verifyCertificate: false, }); - - this.proxyManager = new ProxyManager(this); } /** @@ -219,6 +222,7 @@ export default class ContainerWorkspaces { this.initContainerRouter(); this.initMainRouter(); this.initAgentRouter(); + this.initPMRouter(); } /** diff --git a/daemon/src/http.ts b/daemon/src/http.ts index 1d6f887..170f3f4 100644 --- a/daemon/src/http.ts +++ b/daemon/src/http.ts @@ -50,6 +50,7 @@ export default function setupHttp(this: ContainerWorkspaces): void { this.containerRouter ); this.webApp.use('/api/config', this.authMiddleware(), this.configRouter); + this.webApp.use('/api/pm', this.authMiddleware(), this.pmRouter); this.webApp.use('/api', this.authMiddleware(), this.mainRouter); this.httpServer.on('request', this.webApp); diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index 048fef8..a7e02cf 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -1,5 +1,5 @@ -import type { Configuration } from '../typing/types'; -import express, { Application, Handler, Router } from 'express'; +import type { Configuration, ProxyConfiguration } from '../typing/types'; +import express, { Application, Handler } from 'express'; import { Server as HttpServer, createServer as createHttpServer, @@ -11,7 +11,6 @@ import { } from 'https'; import { existsSync, readFileSync } from 'fs'; import { printError, printSuccess } from '../util/utils'; -import { initializeApiRouter } from './routers/apiRouter'; import proxyHandler from './proxyHandler'; import httpHandler from './httpHandler'; import validateProxy, { validateWSProxy } from './common/validateProxyHost'; @@ -22,14 +21,14 @@ import { WebSocketServer } from 'ws'; import { getProxyInfo } from './common/utils'; import serviceToPort from './common/serviceToPort'; import { Duplex } from 'stream'; +import { ProxyInfo } from './common/types'; export default class ProxyManager { private httpServer: HttpServer | HttpsServer; private wss: WebSocketServer; protected readonly config: Configuration['proxy']; protected webApp: Application; - protected apiRouter: Router; - protected allowedTokens: Map; + private accessTokens: Map; protected containerProxyClient: Map; protected authMiddleware: () => Handler; @@ -43,12 +42,10 @@ export default class ProxyManager { protected validateProxy = validateProxy; protected validateWSProxy = validateWSProxy; - private initializeApiRouter = initializeApiRouter; - constructor(public cw: ContainerWorkspaces) { this.config = cw.proxyConfig; this.authMiddleware = cw.authMiddleware; - this.allowedTokens = new Map(); + this.accessTokens = new Map(); this.containerProxyClient = new Map(); this.startWebServer(); @@ -92,12 +89,6 @@ export default class ProxyManager { this.webApp.use(this.httpHandler.bind(this)); - this.webApp.use( - '/pm/api', - this.authMiddleware(), - this.apiRouter.bind(this) - ); - this.httpServer.on('request', this.webApp); this.initWebSocketServer(); @@ -109,7 +100,7 @@ export default class ProxyManager { } private initRouters(): void { - this.initializeApiRouter(); + return; } private initWebSocketServer(): void { @@ -185,13 +176,11 @@ export default class ProxyManager { throw new Error('Method not implemented.'); } - protected addContainerAccess(containerID: number, token: string): void { - this.allowedTokens.set(token, containerID); + public addContainerAccess(proxyInfo: ProxyInfo, token: string): void { + this.accessTokens.set(token, proxyInfo); } - protected removeContainerAccess(token: string): void { - if (this.allowedTokens.has(token)) { - this.allowedTokens.delete(token); - } + public removeContainerAccess(token: string): void { + this.accessTokens.delete(token); } } diff --git a/daemon/src/lib/proxy-manager/common/types.ts b/daemon/src/lib/proxy-manager/common/types.ts index f06633e..3eb475e 100644 --- a/daemon/src/lib/proxy-manager/common/types.ts +++ b/daemon/src/lib/proxy-manager/common/types.ts @@ -1,4 +1,6 @@ +import serviceToPort from './serviceToPort'; + export interface ProxyInfo { - service: string; + service: keyof typeof serviceToPort; containerID: number; } diff --git a/daemon/src/lib/proxy-manager/common/validateProxyHost.ts b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts index 18dd6c4..47705ca 100644 --- a/daemon/src/lib/proxy-manager/common/validateProxyHost.ts +++ b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts @@ -3,7 +3,6 @@ import ProxyManager from '../ProxyManager'; import { getProxyInfo } from './utils'; import serviceToPort from './serviceToPort'; import { IncomingMessage } from 'http'; -import WebSocket from 'ws'; import { Duplex } from 'stream'; export default function validateProxy( diff --git a/daemon/src/lib/proxy-manager/routers/apiRouter.ts b/daemon/src/lib/proxy-manager/routers/apiRouter.ts deleted file mode 100644 index c4055be..0000000 --- a/daemon/src/lib/proxy-manager/routers/apiRouter.ts +++ /dev/null @@ -1,45 +0,0 @@ -import ProxyManager from '../ProxyManager'; -import { Router, Request, Response } from 'express'; - -export function initializeApiRouter(this: ProxyManager) { - const router: Router = Router(); - this.apiRouter = router; - - router.all('*', (req, res, next) => { - if (req.hostname !== this.config.remoteAddress) { - return next('router'); - } - }); - - router.post('/addcontaineraccess', (req: Request, res: Response) => { - const { - token, - containerID, - }: { - token: string; - containerID: string; - } = req.body; - - const containerIDNumber = parseInt(containerID); - if (isNaN(containerIDNumber)) { - res.status(400).send('Invalid container ID'); - return; - } - - this.addContainerAccess(containerIDNumber, token); - - res.send({ status: 'ok' }); - }); - - router.delete('/removecontaineraccess', (req: Request, res: Response) => { - const { - token, - }: { - token: string; - } = req.body; - - this.removeContainerAccess(token); - - res.send({ status: 'ok' }); - }); -} diff --git a/daemon/src/lib/routers/pm.ts b/daemon/src/lib/routers/pm.ts new file mode 100644 index 0000000..ff1d36c --- /dev/null +++ b/daemon/src/lib/routers/pm.ts @@ -0,0 +1,71 @@ +import ContainerWorkspaces from '../../ContainerWorkspaces'; +import { Router, Request, Response } from 'express'; +import { generatePassword, requireBodyProps } from '../util/utils'; +import serviceToPort from '../proxy-manager/common/serviceToPort'; +import { ProxyInfo } from '../proxy-manager/common/types'; +import ProxyManager from '../proxy-manager/ProxyManager'; + +export function initPMRouter(this: ContainerWorkspaces) { + this.pmRouter = Router(); + const router: Router = this.pmRouter; + const pm: ProxyManager = this.proxyManager; + + router.post( + '/addcontaineraccess', + requireBodyProps('containerID', 'service'), + (req: Request, res: Response) => { + const { + containerID, + service, + }: { + containerID: string; + service: keyof typeof serviceToPort; + } = req.body; + + const containerIDNumber = parseInt(containerID); + if (isNaN(containerIDNumber)) + return res + .status(400) + .send({ status: 'error', message: 'Invalid container ID' }); + + if (typeof service !== 'string') + return res.status(400).send({ + status: 'error', + message: 'service must be a valid string', + }); + // eslint-disable-next-line no-prototype-builtins + else if (!serviceToPort.hasOwnProperty(service)) + return res.status(400).send({ + status: 'error', + message: 'service must be an existing service', + }); + + const token: string = generatePassword(64); + + const proxyInfo: ProxyInfo = { + containerID: containerIDNumber, + service, + }; + + pm.addContainerAccess(proxyInfo, token); + + res.send({ status: 'ok', token }); + } + ); + + router.delete( + '/removecontaineraccess', + requireBodyProps('token'), + (req: Request, res: Response) => { + const { + token, + }: { + token: string; + } = req.body; + + pm.removeContainerAccess(token); + + res.send({ status: 'ok' }); + } + ); +} From 9769396fbb3f0624bf126d183fe6525dd297a66f Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Tue, 30 Aug 2022 17:38:42 +0000 Subject: [PATCH 17/23] Removed unsused import --- daemon/src/lib/proxy-manager/ProxyManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index a7e02cf..28c2711 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -1,4 +1,4 @@ -import type { Configuration, ProxyConfiguration } from '../typing/types'; +import type { Configuration } from '../typing/types'; import express, { Application, Handler } from 'express'; import { Server as HttpServer, From 58213b4db1004b9eabee85ba7de7d47f9de32110 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Tue, 30 Aug 2022 17:40:02 +0000 Subject: [PATCH 18/23] Made CI Lint more strict --- agent/package.json | 2 +- daemon/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/package.json b/agent/package.json index 2ce76f7..6ec67f9 100644 --- a/agent/package.json +++ b/agent/package.json @@ -7,7 +7,7 @@ "dev": "nodemon src/app.ts", "start": "node .", "build": "tsc -p .", - "lint": "eslint . --ext .ts", + "lint": "eslint . --ext .ts --max-warnings=0", "compile": "npm run build && pkg . --output build/usr/bin/cw-agent", "build-deb": "npm run compile && dpkg --build build/ cw-agent.deb" }, diff --git a/daemon/package.json b/daemon/package.json index c6b45ae..df15cea 100644 --- a/daemon/package.json +++ b/daemon/package.json @@ -7,7 +7,7 @@ "dev": "nodemon src/app.ts", "start": "node .", "build": "tsc -p .", - "lint": "eslint . --ext .ts", + "lint": "eslint . --ext .ts --max-warnings=0", "compile": "npm run build && pkg . --output build/usr/bin/cw-daemon", "build-deb": "npm run compile && dpkg --build build/ cw-daemon.deb" }, From 629dd02f2606f99da9451de89169d16243bbf38f Mon Sep 17 00:00:00 2001 From: Idan Carlebach Date: Tue, 30 Aug 2022 22:08:19 +0000 Subject: [PATCH 19/23] Added PM authentication with tokens Co-authored-by: Uri Bar Ilan --- daemon/package-lock.json | 41 ++++++++++++++ daemon/package.json | 2 + daemon/src/lib/proxy-manager/ProxyManager.ts | 42 ++++++++++++-- daemon/src/lib/proxy-manager/common/utils.ts | 12 ++++ .../proxy-manager/common/validateProxyHost.ts | 4 +- daemon/src/lib/proxy-manager/routers/proxy.ts | 30 ++++++++++ .../proxy-manager/routers/serviceRedirect.ts | 55 +++++++++++++++++++ 7 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 daemon/src/lib/proxy-manager/routers/proxy.ts create mode 100644 daemon/src/lib/proxy-manager/routers/serviceRedirect.ts diff --git a/daemon/package-lock.json b/daemon/package-lock.json index 679b4e8..1b1965e 100644 --- a/daemon/package-lock.json +++ b/daemon/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@hostarteam/mysqlclient": "^1.0.6", "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.17.2", "http-proxy": "^1.18.1", @@ -24,6 +25,7 @@ }, "devDependencies": { "@types/bcryptjs": "^2.4.2", + "@types/cookie-parser": "^1.4.3", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", "@types/http-proxy": "^1.17.9", @@ -263,6 +265,15 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cors": { "version": "2.8.12", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", @@ -966,6 +977,18 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -3324,6 +3347,15 @@ "@types/node": "*" } }, + "@types/cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/cors": { "version": "2.8.12", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", @@ -3833,6 +3865,15 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" }, + "cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", diff --git a/daemon/package.json b/daemon/package.json index df15cea..9c5c55a 100644 --- a/daemon/package.json +++ b/daemon/package.json @@ -24,6 +24,7 @@ "dependencies": { "@hostarteam/mysqlclient": "^1.0.6", "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.17.2", "http-proxy": "^1.18.1", @@ -34,6 +35,7 @@ }, "devDependencies": { "@types/bcryptjs": "^2.4.2", + "@types/cookie-parser": "^1.4.3", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", "@types/http-proxy": "^1.17.9", diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index 28c2711..d406a70 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -1,5 +1,5 @@ import type { Configuration } from '../typing/types'; -import express, { Application, Handler } from 'express'; +import express, { Application, Handler, Router } from 'express'; import { Server as HttpServer, createServer as createHttpServer, @@ -18,19 +18,24 @@ import ContainerWorkspaces from '../../ContainerWorkspaces'; import BaseProxy from './proxies/BaseProxy'; import VSCodeProxy from './proxies/vscode'; import { WebSocketServer } from 'ws'; -import { getProxyInfo } from './common/utils'; +import { getProxyInfo, parseCookieString } from './common/utils'; import serviceToPort from './common/serviceToPort'; import { Duplex } from 'stream'; import { ProxyInfo } from './common/types'; +import { initProxyRouter } from './routers/proxy'; +import { initServiceRedirectRouter } from './routers/serviceRedirect'; +import cookieParser from 'cookie-parser'; export default class ProxyManager { private httpServer: HttpServer | HttpsServer; private wss: WebSocketServer; protected readonly config: Configuration['proxy']; protected webApp: Application; - private accessTokens: Map; + protected accessTokens: Map; protected containerProxyClient: Map; protected authMiddleware: () => Handler; + protected proxyRouter: Router; + protected serviceRedirectRouter: Router; protected proxyClients = { vscode: VSCodeProxy, @@ -39,6 +44,9 @@ export default class ProxyManager { protected httpHandler = httpHandler; protected proxyHandler = proxyHandler; + protected initProxyRouter = initProxyRouter; + protected initServiceRedirectRouter = initServiceRedirectRouter; + protected validateProxy = validateProxy; protected validateWSProxy = validateWSProxy; @@ -86,7 +94,10 @@ export default class ProxyManager { this.webApp.disable('x-powered-by'); this.webApp.use(express.json()); this.webApp.use(express.urlencoded({ extended: true })); + this.webApp.use(cookieParser()); + this.webApp.use('/proxy', this.proxyRouter); + this.webApp.use(this.serviceRedirectRouter); this.webApp.use(this.httpHandler.bind(this)); this.httpServer.on('request', this.webApp); @@ -100,7 +111,8 @@ export default class ProxyManager { } private initRouters(): void { - return; + this.initProxyRouter(); + this.initServiceRedirectRouter(); } private initWebSocketServer(): void { @@ -113,7 +125,7 @@ export default class ProxyManager { const proxyInfo = getProxyInfo( // eslint-disable-next-line @typescript-eslint/no-explicit-any - (request.socket as any).servername + (socket as any).servername ); const targetAddress = @@ -128,6 +140,26 @@ export default class ProxyManager { if (!this.validateWSProxy(request, socket)) return socket.destroy(); + const cookies = + request.headers['cookie'] && + parseCookieString(request.headers['cookie']); + + if (!cookies) return socket.destroy(); + + const token = cookies['pm-token']; + + if (!token) return socket.destroy(); + + if (!this.accessTokens.has(token)) return socket.destroy(); + + const { service, containerID } = this.accessTokens.get(token); + + if ( + service != proxyInfo.service || + containerID != proxyInfo.containerID + ) + return socket.destroy(); + let proxyClient = this.containerProxyClient.get( proxyInfo.containerID ); diff --git a/daemon/src/lib/proxy-manager/common/utils.ts b/daemon/src/lib/proxy-manager/common/utils.ts index 756999a..d86b072 100644 --- a/daemon/src/lib/proxy-manager/common/utils.ts +++ b/daemon/src/lib/proxy-manager/common/utils.ts @@ -10,3 +10,15 @@ export function getProxyInfo(hostname: string): ProxyInfo | null { return { service, containerID }; } + +export function parseCookieString(cookie: string): unknown { + return cookie + .split(';') + .map((v) => v.split('=')) + .reduce((acc, v) => { + acc[decodeURIComponent(v[0].trim())] = decodeURIComponent( + v[1].trim() + ); + return acc; + }, {}); +} diff --git a/daemon/src/lib/proxy-manager/common/validateProxyHost.ts b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts index 47705ca..9364de5 100644 --- a/daemon/src/lib/proxy-manager/common/validateProxyHost.ts +++ b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts @@ -41,12 +41,12 @@ export function validateWSProxy( req: IncomingMessage, socket: Duplex ) { - if (req.headers.host === this.config.remoteAddress) { + if ((socket as any).servername === this.config.remoteAddress) { socket.destroy(); return false; } - const proxyInfo = getProxyInfo(req.headers.host); + const proxyInfo = getProxyInfo((socket as any).servername); if (!proxyInfo) { socket.destroy(); return false; diff --git a/daemon/src/lib/proxy-manager/routers/proxy.ts b/daemon/src/lib/proxy-manager/routers/proxy.ts new file mode 100644 index 0000000..d8ac73c --- /dev/null +++ b/daemon/src/lib/proxy-manager/routers/proxy.ts @@ -0,0 +1,30 @@ +import { Router } from 'express'; +import { Request, Response } from 'express'; +import { ProxyInfo } from '../common/types'; +import ProxyManager from '../ProxyManager'; + +export function initProxyRouter(this: ProxyManager) { + this.proxyRouter = Router(); + const router = this.proxyRouter; + + router.get('/redirect', (req: Request, res: Response) => { + const token = req.query.token as string; + + if (!token) return res.status(400).send('token must be specified'); + + if (!this.accessTokens.has(token)) + return res.status(400).send('invalid token'); + + const proxyInfo: ProxyInfo = this.accessTokens.get(token); + + return res + .status(302) + .redirect( + `${this.config.protocol}://${proxyInfo.service}-${ + proxyInfo.containerID + }.${this.config.wildcardBaseAddress}${ + this.config.remotePort ? `:${this.config.remotePort}` : '' + }/_redirect?_pm_token=${token}` + ); + }); +} diff --git a/daemon/src/lib/proxy-manager/routers/serviceRedirect.ts b/daemon/src/lib/proxy-manager/routers/serviceRedirect.ts new file mode 100644 index 0000000..84a342e --- /dev/null +++ b/daemon/src/lib/proxy-manager/routers/serviceRedirect.ts @@ -0,0 +1,55 @@ +import { NextFunction, Router } from 'express'; +import { Request, Response } from 'express'; +import { ProxyInfo } from '../common/types'; +import { getProxyInfo } from '../common/utils'; +import ProxyManager from '../ProxyManager'; + +export function initServiceRedirectRouter(this: ProxyManager): void { + this.serviceRedirectRouter = Router(); + const router = this.serviceRedirectRouter; + + router.get('/_redirect', (req: Request, res: Response) => { + const token = req.query['_pm_token'] as string; + + if (!token) return res.status(401).send('token not specified in query'); + + if (!this.accessTokens.has(token)) + return res.status(401).send('invalid token'); + + const proxyInfo: ProxyInfo = this.accessTokens.get(token); + + const { service, containerID } = getProxyInfo(req.hostname); + if ( + service != proxyInfo.service || + containerID != proxyInfo.containerID + ) + return res.send(401).send('invalid token'); + + return res + .cookie('pm-token', token, { + expires: new Date(Date.now() + 1000 * 60 * 60 * 4), // 4 hours + }) + .status(302) + .redirect('/'); + }); + + router.all('*', (req: Request, res: Response, next: NextFunction) => { + const token = req.cookies?.['pm-token']; + + if (!token) return res.status(401).send('unauthorized'); + + if (!this.accessTokens.has(token)) + return res.status(401).send('unauthorized'); + + const proxyInfo: ProxyInfo = this.accessTokens.get(token); + + const { service, containerID } = getProxyInfo(req.hostname); + if ( + service != proxyInfo.service || + containerID != proxyInfo.containerID + ) + return res.status(401).send('unauthorized'); + + next('router'); + }); +} From 79490fcfda90c44cfb2cc693547ec728a8fd4c18 Mon Sep 17 00:00:00 2001 From: Idan Carlebach Date: Tue, 30 Aug 2022 22:10:39 +0000 Subject: [PATCH 20/23] Fixed lint problems --- daemon/src/lib/proxy-manager/common/validateProxyHost.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daemon/src/lib/proxy-manager/common/validateProxyHost.ts b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts index 9364de5..bf88ceb 100644 --- a/daemon/src/lib/proxy-manager/common/validateProxyHost.ts +++ b/daemon/src/lib/proxy-manager/common/validateProxyHost.ts @@ -41,11 +41,13 @@ export function validateWSProxy( req: IncomingMessage, socket: Duplex ) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((socket as any).servername === this.config.remoteAddress) { socket.destroy(); return false; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any const proxyInfo = getProxyInfo((socket as any).servername); if (!proxyInfo) { socket.destroy(); From d6edfab34a3a6f858e9310739800a0542d68ee19 Mon Sep 17 00:00:00 2001 From: Uri Bar Ilan Date: Tue, 30 Aug 2022 22:45:31 +0000 Subject: [PATCH 21/23] Added stricter equal checking --- daemon/src/lib/proxy-manager/ProxyManager.ts | 4 ++-- daemon/src/lib/proxy-manager/routers/serviceRedirect.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index d406a70..d63124c 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -155,8 +155,8 @@ export default class ProxyManager { const { service, containerID } = this.accessTokens.get(token); if ( - service != proxyInfo.service || - containerID != proxyInfo.containerID + service !== proxyInfo.service || + containerID !== proxyInfo.containerID ) return socket.destroy(); diff --git a/daemon/src/lib/proxy-manager/routers/serviceRedirect.ts b/daemon/src/lib/proxy-manager/routers/serviceRedirect.ts index 84a342e..a648958 100644 --- a/daemon/src/lib/proxy-manager/routers/serviceRedirect.ts +++ b/daemon/src/lib/proxy-manager/routers/serviceRedirect.ts @@ -20,8 +20,8 @@ export function initServiceRedirectRouter(this: ProxyManager): void { const { service, containerID } = getProxyInfo(req.hostname); if ( - service != proxyInfo.service || - containerID != proxyInfo.containerID + service !== proxyInfo.service || + containerID !== proxyInfo.containerID ) return res.send(401).send('invalid token'); @@ -45,8 +45,8 @@ export function initServiceRedirectRouter(this: ProxyManager): void { const { service, containerID } = getProxyInfo(req.hostname); if ( - service != proxyInfo.service || - containerID != proxyInfo.containerID + service !== proxyInfo.service || + containerID !== proxyInfo.containerID ) return res.status(401).send('unauthorized'); From 0f95932ebeb5e951c3e5dd817cf560a46ef296bc Mon Sep 17 00:00:00 2001 From: Idan Carlebach Date: Sat, 3 Sep 2022 14:11:53 +0000 Subject: [PATCH 22/23] Revmoed unnecessary import and ProxyManager class member --- daemon/src/lib/proxy-manager/ProxyManager.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index d63124c..f0d65fd 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -17,7 +17,6 @@ import validateProxy, { validateWSProxy } from './common/validateProxyHost'; import ContainerWorkspaces from '../../ContainerWorkspaces'; import BaseProxy from './proxies/BaseProxy'; import VSCodeProxy from './proxies/vscode'; -import { WebSocketServer } from 'ws'; import { getProxyInfo, parseCookieString } from './common/utils'; import serviceToPort from './common/serviceToPort'; import { Duplex } from 'stream'; @@ -28,7 +27,6 @@ import cookieParser from 'cookie-parser'; export default class ProxyManager { private httpServer: HttpServer | HttpsServer; - private wss: WebSocketServer; protected readonly config: Configuration['proxy']; protected webApp: Application; protected accessTokens: Map; From 8f2381e96bd05f6344baed5634c70764570a22dd Mon Sep 17 00:00:00 2001 From: Idan Carlebach <53697782+idandev@users.noreply.github.com> Date: Wed, 5 Oct 2022 22:14:49 +0300 Subject: [PATCH 23/23] Lint problems --- daemon/src/lib/proxy-manager/ProxyManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/daemon/src/lib/proxy-manager/ProxyManager.ts b/daemon/src/lib/proxy-manager/ProxyManager.ts index a6ecbd4..f1d5c69 100644 --- a/daemon/src/lib/proxy-manager/ProxyManager.ts +++ b/daemon/src/lib/proxy-manager/ProxyManager.ts @@ -18,7 +18,6 @@ import ContainerWorkspaces from '../../ContainerWorkspaces'; import BaseProxy from './proxies/BaseProxy'; import VSCodeProxy from './proxies/vscode'; import WebShellProxy from './proxies/webshell'; -import { WebSocketServer } from 'ws'; import { getProxyInfo, parseCookieString } from './common/utils'; import serviceToPort from './common/serviceToPort'; import { Duplex } from 'stream';