From f51066ecd170e86259ba7b9401c7fef7344793c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 May 2022 14:29:50 +0100 Subject: [PATCH] feat: add HTTPS support for Edge Functions in Netlify Dev (#4567) * feat: add https support for Edge Functions * refactor: use `x-forwarded` headers * chore: update cert generation * refactor: change certs script * chore: fix cert generation on Windows * chore: fix cert generation on Windows * chore: fix cert generation on Windows * chore: omg * chore: move self generation to integration tests * chore: add cert generation to test:dev script * chore: update @netlify/edge-bundler to v1.0.0 --- .github/workflows/main.yml | 4 + .gitignore | 2 + certconf | 8 ++ npm-shrinkwrap.json | 128 ++++++++++++++++++++-- package.json | 7 +- src/lib/edge-functions/headers.js | 3 +- src/lib/edge-functions/proxy.js | 9 +- src/utils/detect-server-settings.js | 2 +- tests/integration/200.command.dev.test.js | 28 ++++- tests/integration/assets/cert.pem | 16 --- tests/integration/assets/key.pem | 28 ----- 11 files changed, 175 insertions(+), 60 deletions(-) create mode 100644 certconf delete mode 100644 tests/integration/assets/cert.pem delete mode 100644 tests/integration/assets/key.pem diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b80f81ad11d..7208f96fe2a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -97,6 +97,10 @@ jobs: if_true: 'npm run test:affected ${{ github.event.pull_request.base.sha }}' # on pull requests test with the project graph only the affected tests if_false: 'npm run test:ci:ava:integration' # on the base branch run all the tests as security measure if: '${{ !steps.release-check.outputs.IS_RELEASE }}' + - name: Generate self-signed certificates + run: npm run certs + if: '${{!steps.release-check.outputs.IS_RELEASE}}' + shell: bash - name: Prepare tests run: npm run test:init if: '${{ !steps.release-check.outputs.IS_RELEASE }}' diff --git a/.gitignore b/.gitignore index 6e44b3adc1d..0e0abf58aae 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,6 @@ tests/integration/hugo-site/resources tests/integration/hugo-site/out tests/integration/hugo-site/.hugo_build.lock _test_out/** +*.crt +*.key diff --git a/certconf b/certconf new file mode 100644 index 00000000000..a2d9eb4fa24 --- /dev/null +++ b/certconf @@ -0,0 +1,8 @@ +[dn] +CN=localhost +[req] +distinguished_name = dn +[EXT] +subjectAltName=DNS:localhost +keyUsage=digitalSignature +extendedKeyUsage=serverAuth \ No newline at end of file diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index eb84d692288..b15b558b6e0 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -12,7 +12,7 @@ "dependencies": { "@netlify/build": "^27.0.1", "@netlify/config": "^18.0.0", - "@netlify/edge-bundler": "^0.12.0", + "@netlify/edge-bundler": "^1.0.0", "@netlify/framework-info": "^9.0.2", "@netlify/local-functions-proxy": "^1.1.1", "@netlify/plugins-list": "^6.19.0", @@ -1245,6 +1245,27 @@ "node": "^12.20.0 || ^14.14.0 || >=16.0.0" } }, + "node_modules/@netlify/build/node_modules/@netlify/edge-bundler": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-0.12.0.tgz", + "integrity": "sha512-4AB3GSVeg5Qyh3A47oySKfRTNPNelI7UPgOONhOG4dCNr1fcVUJH0Fdm4rljRnaoQ1Vfy1dPVvMAT5dQVjK77w==", + "dependencies": { + "common-path-prefix": "^3.0.0", + "del": "^6.0.0", + "env-paths": "^3.0.0", + "execa": "^6.0.0", + "glob-to-regexp": "^0.4.1", + "node-fetch": "^3.1.1", + "node-stream-zip": "^1.15.0", + "p-wait-for": "^4.1.0", + "semver": "^7.3.5", + "tmp-promise": "^3.0.3", + "uuid": "^8.3.2" + }, + "engines": { + "node": "^12.20.0 || ^14.14.0 || >=16.0.0" + } + }, "node_modules/@netlify/build/node_modules/@sindresorhus/is": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", @@ -1351,6 +1372,17 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "node_modules/@netlify/build/node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@netlify/build/node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -1425,6 +1457,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@netlify/build/node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, "node_modules/@netlify/build/node_modules/got": { "version": "10.7.0", "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", @@ -1580,6 +1617,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@netlify/build/node_modules/node-fetch": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.4.tgz", + "integrity": "sha512-WvYJRN7mMyOLurFR2YpysQGuwYrJN+qrrpHjJDuKMcSPdfFccRUla/kng2mz6HWSBxJcqPbvatS6Gb4RhOzCJw==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/@netlify/build/node_modules/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -1689,6 +1743,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@netlify/build/node_modules/p-wait-for": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-4.1.0.tgz", + "integrity": "sha512-i8nE5q++9h8oaQHWltS1Tnnv4IoMDOlqN7C0KFG2OdbK0iFJIt6CROZ8wfBM+K4Pxqfnq4C4lkkpXqTEpB5DZw==", + "dependencies": { + "p-timeout": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@netlify/build/node_modules/path-exists": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", @@ -2304,9 +2372,9 @@ } }, "node_modules/@netlify/edge-bundler": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-0.12.0.tgz", - "integrity": "sha512-4AB3GSVeg5Qyh3A47oySKfRTNPNelI7UPgOONhOG4dCNr1fcVUJH0Fdm4rljRnaoQ1Vfy1dPVvMAT5dQVjK77w==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-1.0.0.tgz", + "integrity": "sha512-9pVyU0yXqdGG3LFEwtRBzUpGyplPE7hCGEvkxZpGvKjbiC0/jj6x/b6g7Ecjd3wzb6u1QpPq6XQhI+ZIl/PI3Q==", "dependencies": { "common-path-prefix": "^3.0.0", "del": "^6.0.0", @@ -23781,6 +23849,24 @@ "yargs": "^17.3.1" }, "dependencies": { + "@netlify/edge-bundler": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-0.12.0.tgz", + "integrity": "sha512-4AB3GSVeg5Qyh3A47oySKfRTNPNelI7UPgOONhOG4dCNr1fcVUJH0Fdm4rljRnaoQ1Vfy1dPVvMAT5dQVjK77w==", + "requires": { + "common-path-prefix": "^3.0.0", + "del": "^6.0.0", + "env-paths": "^3.0.0", + "execa": "^6.0.0", + "glob-to-regexp": "^0.4.1", + "node-fetch": "^3.1.1", + "node-stream-zip": "^1.15.0", + "p-wait-for": "^4.1.0", + "semver": "^7.3.5", + "tmp-promise": "^3.0.3", + "uuid": "^8.3.2" + } + }, "@sindresorhus/is": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", @@ -23845,6 +23931,11 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==" + }, "escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -23889,6 +23980,11 @@ "path-exists": "^5.0.0" } }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, "got": { "version": "10.7.0", "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", @@ -23980,6 +24076,16 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" }, + "node-fetch": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.4.tgz", + "integrity": "sha512-WvYJRN7mMyOLurFR2YpysQGuwYrJN+qrrpHjJDuKMcSPdfFccRUla/kng2mz6HWSBxJcqPbvatS6Gb4RhOzCJw==", + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, "npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -24041,6 +24147,14 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.0.2.tgz", "integrity": "sha512-sEmji9Yaq+Tw+STwsGAE56hf7gMy9p0tQfJojIAamB7WHJYJKf1qlsg9jqBWG8q9VCxKPhZaP/AcXwEoBcYQhQ==" }, + "p-wait-for": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-4.1.0.tgz", + "integrity": "sha512-i8nE5q++9h8oaQHWltS1Tnnv4IoMDOlqN7C0KFG2OdbK0iFJIt6CROZ8wfBM+K4Pxqfnq4C4lkkpXqTEpB5DZw==", + "requires": { + "p-timeout": "^5.0.0" + } + }, "path-exists": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", @@ -24399,9 +24513,9 @@ } }, "@netlify/edge-bundler": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-0.12.0.tgz", - "integrity": "sha512-4AB3GSVeg5Qyh3A47oySKfRTNPNelI7UPgOONhOG4dCNr1fcVUJH0Fdm4rljRnaoQ1Vfy1dPVvMAT5dQVjK77w==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-1.0.0.tgz", + "integrity": "sha512-9pVyU0yXqdGG3LFEwtRBzUpGyplPE7hCGEvkxZpGvKjbiC0/jj6x/b6g7Ecjd3wzb6u1QpPq6XQhI+ZIl/PI3Q==", "requires": { "common-path-prefix": "^3.0.0", "del": "^6.0.0", diff --git a/package.json b/package.json index 21027802ef5..ce99c202595 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "format:check-fix:prettier": "run-e format:check:prettier format:fix:prettier", "format:check:prettier": "cross-env-shell prettier --check $npm_package_config_prettier", "format:fix:prettier": "cross-env-shell prettier --write $npm_package_config_prettier", - "test:dev": "run-s test:init:* test:dev:*", + "test:dev": "run-s certs test:init:* test:dev:*", "test:init": "run-s test:init:*", "test:init:cli-version": "npm run start -- --version", "test:init:cli-help": "npm run start -- --help", @@ -200,7 +200,8 @@ "site:build": "run-s site:build:*", "site:build:install": "cd site && npm ci --no-audit", "site:build:assets": "cd site && npm run build", - "postinstall": "node ./scripts/postinstall.js" + "postinstall": "node ./scripts/postinstall.js", + "certs": "openssl req -x509 -out localhost.crt -keyout localhost.key -newkey rsa:2048 -nodes -sha256 -subj \"/CN=localhost\" -extensions EXT -config certconf" }, "config": { "eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,html}\" \"*.{mjs,cjs,js,md,html}\" \".*.{mjs,cjs,js,md,html}\"", @@ -209,7 +210,7 @@ "dependencies": { "@netlify/build": "^27.0.1", "@netlify/config": "^18.0.0", - "@netlify/edge-bundler": "^0.12.0", + "@netlify/edge-bundler": "^1.0.0", "@netlify/framework-info": "^9.0.2", "@netlify/local-functions-proxy": "^1.1.1", "@netlify/plugins-list": "^6.19.0", diff --git a/src/lib/edge-functions/headers.js b/src/lib/edge-functions/headers.js index 13b93eac534..f6e919ae3f0 100644 --- a/src/lib/edge-functions/headers.js +++ b/src/lib/edge-functions/headers.js @@ -1,7 +1,8 @@ module.exports = { + ForwardedHost: 'x-forwarded-host', + ForwardedProtocol: 'x-forwarded-proto', Functions: 'x-deno-functions', Geo: 'x-nf-geo', - PassHost: 'X-NF-Pass-Host', Passthrough: 'x-deno-pass', RequestID: 'X-NF-Request-ID', } diff --git a/src/lib/edge-functions/proxy.js b/src/lib/edge-functions/proxy.js index a8f8d3bec05..d3f95873581 100644 --- a/src/lib/edge-functions/proxy.js +++ b/src/lib/edge-functions/proxy.js @@ -52,6 +52,7 @@ const initializeProxy = async ({ config, configPath, geolocationMode, getUpdated // the network if needed. We don't want to wait for that to be completed, or // the command will be left hanging. const server = prepareServer({ + certificatePath: settings.https ? settings.https.certFilePath : undefined, config, configPath, directories: [internalFunctionsPath, userFunctionsPath].filter(Boolean), @@ -100,11 +101,15 @@ const initializeProxy = async ({ config, configPath, geolocationMode, getUpdated req[headersSymbol] = { [headers.Functions]: functionNames.join(','), - [headers.PassHost]: `${LOCAL_HOST}:${mainPort}`, + [headers.ForwardedHost]: `localhost:${mainPort}`, [headers.Passthrough]: 'passthrough', [headers.RequestID]: generateUUID(), } + if (settings.https) { + req[headersSymbol][headers.ForwardedProtocol] = 'https' + } + return `http://${LOCAL_HOST}:${isolatePort}` } } @@ -112,6 +117,7 @@ const initializeProxy = async ({ config, configPath, geolocationMode, getUpdated const isEdgeFunctionsRequest = (req) => req[headersSymbol] !== undefined const prepareServer = async ({ + certificatePath, config, configPath, directories, @@ -124,6 +130,7 @@ const prepareServer = async ({ const distImportMapPath = getPathInProject([DIST_IMPORT_MAP_PATH]) const runIsolate = await bundler.serve({ ...getDownloadUpdateFunctions(), + certificatePath, debug: env.NETLIFY_DENO_DEBUG === 'true', distImportMapPath, formatExportTypeError: (name) => diff --git a/src/utils/detect-server-settings.js b/src/utils/detect-server-settings.js index 282a1fb691f..309428aea67 100644 --- a/src/utils/detect-server-settings.js +++ b/src/utils/detect-server-settings.js @@ -46,7 +46,7 @@ const readHttpsSettings = async (options) => { throw new Error(`Error reading certificate file: ${certError.message}`) } - return { key, cert } + return { key, cert, keyFilePath: path.resolve(keyFile), certFilePath: path.resolve(certFile) } } const validateStringProperty = ({ devConfig, property }) => { diff --git a/tests/integration/200.command.dev.test.js b/tests/integration/200.command.dev.test.js index c9d0e5bd90b..82b29175a60 100644 --- a/tests/integration/200.command.dev.test.js +++ b/tests/integration/200.command.dev.test.js @@ -7,6 +7,7 @@ const path = require('path') // eslint-disable-next-line ava/use-test const avaTest = require('ava') const { isCI } = require('ci-info') +const { Response } = require('node-fetch') const { curl } = require('./utils/curl') const { withDevServer } = require('./utils/dev-server') @@ -194,7 +195,13 @@ export const handler = async function () { config: { build: { publish: 'public' }, functions: { directory: 'functions' }, - dev: { https: { certFile: 'cert.pem', keyFile: 'key.pem' } }, + dev: { https: { certFile: 'localhost.crt', keyFile: 'localhost.key' } }, + edge_functions: [ + { + function: 'hello', + path: '/', + }, + ], }, }) .withContentFile({ @@ -211,15 +218,30 @@ export const handler = async function () { body: 'Hello World', }), }) + .withEdgeFunction({ + handler: async (req, { next }) => { + if (!req.url.includes('?ef=true')) { + return + } + + // eslint-disable-next-line n/callback-return + const res = await next() + const text = await res.text() + + return new Response(text.toUpperCase(), res) + }, + name: 'hello', + }) .buildAsync() await Promise.all([ - copyFile(`${__dirname}/assets/cert.pem`, `${builder.directory}/cert.pem`), - copyFile(`${__dirname}/assets/key.pem`, `${builder.directory}/key.pem`), + copyFile(`${__dirname}/../../localhost.crt`, `${builder.directory}/localhost.crt`), + copyFile(`${__dirname}/../../localhost.key`, `${builder.directory}/localhost.key`), ]) await withDevServer({ cwd: builder.directory, args }, async ({ port }) => { const options = { https: { rejectUnauthorized: false } } t.is(await got(`https://localhost:${port}`, options).text(), 'index') + t.is(await got(`https://localhost:${port}?ef=true`, options).text(), 'INDEX') t.is(await got(`https://localhost:${port}/api/hello`, options).text(), 'Hello World') }) }) diff --git a/tests/integration/assets/cert.pem b/tests/integration/assets/cert.pem deleted file mode 100644 index 74fafbc4947..00000000000 --- a/tests/integration/assets/cert.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICljCCAX4CCQCoGgN/iuZYvTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV -UzAeFw0yMTA0MTQxMzA5MzZaFw00ODA4MjkxMzA5MzZaMA0xCzAJBgNVBAYTAlVT -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx3I+8KhT2r6WD06hoVfL -w6oGUvBNiyrocNGm9Awsc2F7+mPr6AdQZQqXwnA1My+qykXpK8wYk4x0Q4hH8RCL -d/ivrdI6FSlXPiQjkC7VDnK3hI3g4wV3yarOiagna+EuzntCrR820/hwlfzz5wF4 -vi7eDZp3vEUjrzSldd1NtjNOkzHKpSMNFqcqy9siNKIltJtcIgH3sBxB2PAxenHt -oUOa5A5geIPp0TuUdliMVPabLHOF/ikqVf7DHatjJNZvC2XPxLUdjk9d8r7cKFOt -sEgODEk7NdaILNs5LbUMxyWOvbkAr9XXF/7El/aoWK8Bb+7U+oyG+sekGevbH+Pu -3wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDChgzE7KA0hUPxbYQ32zmjlledWI6e -aAkg/iva24XeiKiLMh09v/UVfu5ytessAwkKpz94XQtty1y1OWSCeZYqIjBBWLCQ -0q5u2F1SfIHVOi7KU4047od1qPZz97RJvVmu6Wk20v5h1G9O51FSB/C4DrxGd0Qc -SumK+RQsWUYFVbnxV62IDjJwBdtDpscYAgVY3IfqGgVPMN1PUzZ8/GJD+4FOtJBt -TGF1mo3WNPfHCaPyahnf6RSeiw+41FS5rYVf74lfO2T5kzx1sRX0NPoERGSer8pY -+aSnbGE+P6/DsfFl+vfzHltExHN8MHWFzEvpZuyybWIh8llrfv4wgolB ------END CERTIFICATE----- diff --git a/tests/integration/assets/key.pem b/tests/integration/assets/key.pem deleted file mode 100644 index 82173ad16e7..00000000000 --- a/tests/integration/assets/key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHcj7wqFPavpYP -TqGhV8vDqgZS8E2LKuhw0ab0DCxzYXv6Y+voB1BlCpfCcDUzL6rKRekrzBiTjHRD -iEfxEIt3+K+t0joVKVc+JCOQLtUOcreEjeDjBXfJqs6JqCdr4S7Oe0KtHzbT+HCV -/PPnAXi+Lt4Nmne8RSOvNKV13U22M06TMcqlIw0WpyrL2yI0oiW0m1wiAfewHEHY -8DF6ce2hQ5rkDmB4g+nRO5R2WIxU9pssc4X+KSpV/sMdq2Mk1m8LZc/EtR2OT13y -vtwoU62wSA4MSTs11ogs2zkttQzHJY69uQCv1dcX/sSX9qhYrwFv7tT6jIb6x6QZ -69sf4+7fAgMBAAECggEBALNATasTThIaGln1jLSqaJsZM4fVK4w5ayECfC94xXaJ -ldiLd5lZAXteUtjQ6i9v1urmfFL9otTxdgi/8hCJkx+nJZVujP854YwbBM9wF9Jd -bey3avf+kRNx7SCxPYTqpJu1Ek7v7+IAtodHCZqDqxYanz3Qp25RIWo1QcQ1usNV -r3MSLeS3cWnt+19iLlzeHpScPDoa3yLeL+CMlL77P+t6b4j+5O1bkBQpScn3hlKC -t5e1fwqtuWhJPhQt7IjlTG1qHvq1kNfJs50gTjlBS1XbSYeBpuqkf+fVck0I/M8X -Z0hzKuis5Gz7HnwTORFYU1kF3sMNw2VYqNzqZ5KAltECgYEA+JbfL9ho8+ItDv+7 -QFyUqyyDftdqkXLLxXdGb8crKqcMwT4rcRMTF7Bk0A7sAivD8q4DA8RlnE0Mag1u -vR1b0vSfIMNdxyfo0ajbQy+3yk9LgfzW98zyztyPTc3+UasqkPqtWXQJiT+LmPqC -KoAzofo/p59uCGlFTx1KsH9aGtkCgYEAzWRVw7u/ue/16BaPkLHF3PRG9P+zRnyb -mWypWwIl9w4/wXIwo2D4eZHbn0kjRNeOqOIagpBbEwbPULm2E3gfQh9PT7oXHVGe -YOaHeOxxA5NOE8T0aSSPV8giiJpIb6YfHaoI+6tb8Hq3OgfjTtaTmJTPtcFktfy+ -mdMzHvvclHcCgYB5FmyCzsRYv7w5CSJ5+F7GfnlS6LVVQfi2KCJcQkTpktNosdLR -UnxDVr3UhuA5Mn5REKgRRrJED1fTtNVTMyDOQi/c8q0UXIFQ1xJuyKia2EMsfy9r -Jx7C2rbHLGcL+vdSBXk5EIewng5upt+OukHsTYyCJuufF0AWiGsgS/hlCQKBgG+4 -Y+5T8bXyEJBttSm/iSDmvrIFZD6zCPAravNV99Cqr4NJ2asE1CNMc2nxHn4TwkWa -t1DNtLxkt9/xJFjyvg35eeqkDm6kNsH+ozHjapXHSnD2cPvAJzhYZHBpe30/ny1D -kB9U1m24UnH+WGZJ0X7tcw4qY92Z3RNPDj+MQC+BAoGAZYud5kgVE5/LtQhCWcm4 -DAWX/J/d6B+nqzQl8bc0jAaHq1mfLjjZcLWDrMeykzZsWKFI2ees3QYN7JhWmZur -xgvanmEkSon6jmTbuikXU8vv1h/uCA1wo10M/uJMiP7Sgz9q4z7yM0EjS71wAOXX -PMoGCIO8ObI78nRW7ar99W4= ------END PRIVATE KEY-----