Skip to content

Commit f51066e

Browse files
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
1 parent 6695113 commit f51066e

File tree

11 files changed

+175
-60
lines changed

11 files changed

+175
-60
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ jobs:
9797
if_true: 'npm run test:affected ${{ github.event.pull_request.base.sha }}' # on pull requests test with the project graph only the affected tests
9898
if_false: 'npm run test:ci:ava:integration' # on the base branch run all the tests as security measure
9999
if: '${{ !steps.release-check.outputs.IS_RELEASE }}'
100+
- name: Generate self-signed certificates
101+
run: npm run certs
102+
if: '${{!steps.release-check.outputs.IS_RELEASE}}'
103+
shell: bash
100104
- name: Prepare tests
101105
run: npm run test:init
102106
if: '${{ !steps.release-check.outputs.IS_RELEASE }}'

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ tests/integration/hugo-site/resources
3333
tests/integration/hugo-site/out
3434
tests/integration/hugo-site/.hugo_build.lock
3535
_test_out/**
36+
*.crt
37+
*.key
3638

certconf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[dn]
2+
CN=localhost
3+
[req]
4+
distinguished_name = dn
5+
[EXT]
6+
subjectAltName=DNS:localhost
7+
keyUsage=digitalSignature
8+
extendedKeyUsage=serverAuth

npm-shrinkwrap.json

Lines changed: 121 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@
184184
"format:check-fix:prettier": "run-e format:check:prettier format:fix:prettier",
185185
"format:check:prettier": "cross-env-shell prettier --check $npm_package_config_prettier",
186186
"format:fix:prettier": "cross-env-shell prettier --write $npm_package_config_prettier",
187-
"test:dev": "run-s test:init:* test:dev:*",
187+
"test:dev": "run-s certs test:init:* test:dev:*",
188188
"test:init": "run-s test:init:*",
189189
"test:init:cli-version": "npm run start -- --version",
190190
"test:init:cli-help": "npm run start -- --help",
@@ -200,7 +200,8 @@
200200
"site:build": "run-s site:build:*",
201201
"site:build:install": "cd site && npm ci --no-audit",
202202
"site:build:assets": "cd site && npm run build",
203-
"postinstall": "node ./scripts/postinstall.js"
203+
"postinstall": "node ./scripts/postinstall.js",
204+
"certs": "openssl req -x509 -out localhost.crt -keyout localhost.key -newkey rsa:2048 -nodes -sha256 -subj \"/CN=localhost\" -extensions EXT -config certconf"
204205
},
205206
"config": {
206207
"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 @@
209210
"dependencies": {
210211
"@netlify/build": "^27.0.1",
211212
"@netlify/config": "^18.0.0",
212-
"@netlify/edge-bundler": "^0.12.0",
213+
"@netlify/edge-bundler": "^1.0.0",
213214
"@netlify/framework-info": "^9.0.2",
214215
"@netlify/local-functions-proxy": "^1.1.1",
215216
"@netlify/plugins-list": "^6.19.0",

src/lib/edge-functions/headers.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module.exports = {
2+
ForwardedHost: 'x-forwarded-host',
3+
ForwardedProtocol: 'x-forwarded-proto',
24
Functions: 'x-deno-functions',
35
Geo: 'x-nf-geo',
4-
PassHost: 'X-NF-Pass-Host',
56
Passthrough: 'x-deno-pass',
67
RequestID: 'X-NF-Request-ID',
78
}

src/lib/edge-functions/proxy.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const initializeProxy = async ({ config, configPath, geolocationMode, getUpdated
5252
// the network if needed. We don't want to wait for that to be completed, or
5353
// the command will be left hanging.
5454
const server = prepareServer({
55+
certificatePath: settings.https ? settings.https.certFilePath : undefined,
5556
config,
5657
configPath,
5758
directories: [internalFunctionsPath, userFunctionsPath].filter(Boolean),
@@ -100,18 +101,23 @@ const initializeProxy = async ({ config, configPath, geolocationMode, getUpdated
100101

101102
req[headersSymbol] = {
102103
[headers.Functions]: functionNames.join(','),
103-
[headers.PassHost]: `${LOCAL_HOST}:${mainPort}`,
104+
[headers.ForwardedHost]: `localhost:${mainPort}`,
104105
[headers.Passthrough]: 'passthrough',
105106
[headers.RequestID]: generateUUID(),
106107
}
107108

109+
if (settings.https) {
110+
req[headersSymbol][headers.ForwardedProtocol] = 'https'
111+
}
112+
108113
return `http://${LOCAL_HOST}:${isolatePort}`
109114
}
110115
}
111116

112117
const isEdgeFunctionsRequest = (req) => req[headersSymbol] !== undefined
113118

114119
const prepareServer = async ({
120+
certificatePath,
115121
config,
116122
configPath,
117123
directories,
@@ -124,6 +130,7 @@ const prepareServer = async ({
124130
const distImportMapPath = getPathInProject([DIST_IMPORT_MAP_PATH])
125131
const runIsolate = await bundler.serve({
126132
...getDownloadUpdateFunctions(),
133+
certificatePath,
127134
debug: env.NETLIFY_DENO_DEBUG === 'true',
128135
distImportMapPath,
129136
formatExportTypeError: (name) =>

src/utils/detect-server-settings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const readHttpsSettings = async (options) => {
4646
throw new Error(`Error reading certificate file: ${certError.message}`)
4747
}
4848

49-
return { key, cert }
49+
return { key, cert, keyFilePath: path.resolve(keyFile), certFilePath: path.resolve(certFile) }
5050
}
5151

5252
const validateStringProperty = ({ devConfig, property }) => {

tests/integration/200.command.dev.test.js

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const path = require('path')
77
// eslint-disable-next-line ava/use-test
88
const avaTest = require('ava')
99
const { isCI } = require('ci-info')
10+
const { Response } = require('node-fetch')
1011

1112
const { curl } = require('./utils/curl')
1213
const { withDevServer } = require('./utils/dev-server')
@@ -194,7 +195,13 @@ export const handler = async function () {
194195
config: {
195196
build: { publish: 'public' },
196197
functions: { directory: 'functions' },
197-
dev: { https: { certFile: 'cert.pem', keyFile: 'key.pem' } },
198+
dev: { https: { certFile: 'localhost.crt', keyFile: 'localhost.key' } },
199+
edge_functions: [
200+
{
201+
function: 'hello',
202+
path: '/',
203+
},
204+
],
198205
},
199206
})
200207
.withContentFile({
@@ -211,15 +218,30 @@ export const handler = async function () {
211218
body: 'Hello World',
212219
}),
213220
})
221+
.withEdgeFunction({
222+
handler: async (req, { next }) => {
223+
if (!req.url.includes('?ef=true')) {
224+
return
225+
}
226+
227+
// eslint-disable-next-line n/callback-return
228+
const res = await next()
229+
const text = await res.text()
230+
231+
return new Response(text.toUpperCase(), res)
232+
},
233+
name: 'hello',
234+
})
214235
.buildAsync()
215236

216237
await Promise.all([
217-
copyFile(`${__dirname}/assets/cert.pem`, `${builder.directory}/cert.pem`),
218-
copyFile(`${__dirname}/assets/key.pem`, `${builder.directory}/key.pem`),
238+
copyFile(`${__dirname}/../../localhost.crt`, `${builder.directory}/localhost.crt`),
239+
copyFile(`${__dirname}/../../localhost.key`, `${builder.directory}/localhost.key`),
219240
])
220241
await withDevServer({ cwd: builder.directory, args }, async ({ port }) => {
221242
const options = { https: { rejectUnauthorized: false } }
222243
t.is(await got(`https://localhost:${port}`, options).text(), 'index')
244+
t.is(await got(`https://localhost:${port}?ef=true`, options).text(), 'INDEX')
223245
t.is(await got(`https://localhost:${port}/api/hello`, options).text(), 'Hello World')
224246
})
225247
})

0 commit comments

Comments
 (0)