diff --git a/docs/docs/local-https.md b/docs/docs/local-https.md index 79cc643c8349e..0198d43c52ce6 100644 --- a/docs/docs/local-https.md +++ b/docs/docs/local-https.md @@ -8,50 +8,158 @@ Gatsby provides an easy way to use a local HTTPS server during development, than Start the development server using `npm run develop` as usual, and add either the `-S` or `--https` flag. - $ npm run develop -- --https +```shell +npm run develop -- --https +``` ## Setup When setting up a development SSL certificate for the first time, you may be asked to type in your password after starting the development environment: - info setting up SSL certificate (may require sudo) +```text +info setting up SSL certificate (may require elevated permissions/sudo) + +Password: +``` + +On windows, the prompt will differ: + +```text +A password is required to access the secure certificate authority key +used for signing certificates. + +If this is the first time this has run, then this is to set the password +for future use. If any new certificates are signed later, you will need +to use this same password. + +Please enter the CA password: +``` + +The password is _only_ required the first time you are using Gatsby's HTTPS feature on your machine, or when you are creating a brand new certificate. + +## Using `Certutil` + +After typing in your password, `devcert` will install the CA certificate in your operating system trusted certs store. A utility called `certutil` will be needed to update the trust store for various browsers; specifically: Firefox, and Chrome (when it's running on Linux). + +`devcert` is configured to install `certutil` automatically, unless you're running Windows. If an automatic install is not successful, you may need to install it manually. + +### Manual Installation of `Certutil` + +To install `certutil`, you need to install the `nss tools` package(s). The exact procedure will differ depending on your operating system. + +#### Linux + +On a linux OS, you should be able to run one of the following, depending on your Linux distro: + +```shell +# Debian based (Ubuntu) +sudo apt install libnss3-tools + +# RHEL based (Fedora) +sudo yum install nss-tools + +# OpenSuse +sudo zypper install mozilla-nss-tools +``` + +#### MacOS + +Run the following command: + +```shell +brew install nss +``` + +#### Windows + +Pre-compiled libraries are rare, so you may need to compile it yourself. Because of how difficult Windows makes it, `devcert` will not attempt to update the Firefox trust store automatically; instead, it will fall back to using the "Firefox wizard", detailed below. - Password: +### Debugging Installation -This is _only_ required the first time you are using Gatsby's HTTPS feature on your machine. After that, certificates will be created on the fly. +If you choose not to install `certutil`, or the automatic install is not successful, you may get the following errors/prompts: + +#### Chrome on Linux + +```text +WARNING: It looks like you have Chrome installed, but you specified +'skipCertutilInstall: true'. Unfortunately, without installing +certutil, it's impossible get Chrome to trust devcert's certificates +The certificates will work, but Chrome will continue to warn you that +they are untrusted. +``` + +#### Firefox + +If you have Firefox installed, `devcert` will try to utilize Firefox itself to trust the certificate + +```text +devcert was unable to automatically configure Firefox. You'll need to +complete this process manually. Don't worry though - Firefox will walk +you through it. + +When you're ready, hit any key to continue. Firefox will launch and +display a wizard to walk you through how to trust the devcert +certificate. When you are finished, come back here and we'll finish up. +(If Firefox doesn't start, go ahead and start it and navigate to +http://localhost:52175 in a new tab.) + +If you are curious about why all this is necessary, check out +https://github.com/davewasmer/devcert#how-it-works + +``` -After typing in your password, `devcert` will attempt to install some software necessary to tell Firefox (and Chrome, only on Linux) to trust your development certificates. +Your options are as follows: - Unable to automatically install SSL certificate - please follow the - prompts at http://localhost:52175 in Firefox to trust the root certificate - See https://github.com/davewasmer/devcert#how-it-works for more details - -- Press once you finish the Firefox prompts -- +- Press enter and it will launch Firefox for you. -If you wish to support Firefox (or Chrome on Linux), visit `http://localhost:52175` in Firefox and follow the point-and-click wizard. Otherwise, you may press enter without following the prompts. **Reminder: you'll only need to do this once per machine.** +- If you wish to have trust support on Firefox, tell the point-and-click wizard `this certificate can identify websites`, and click OK. Otherwise, you may hit cancel and close the browser, then key return to finish building. **Reminder: you'll only need to do this once per machine.** -Now open the development server at `https://localhost:8000` and enjoy the HTTPS goodness ✨. Of course, you may change the port according to your setup. +## After `devcert` setup process + +You can open the development server at `https://localhost:8000` and enjoy the HTTPS goodness ✨. Of course, you may change the port according to your setup. Find out more about [how devcert works](https://github.com/davewasmer/devcert#how-it-works). +## Management of certificates generated by devcert + +If you want to do some maintenance/cleanup of the certificates generated by `devcert`, please refer to the [devcert-cli](https://github.com/davewasmer/devcert-cli/blob/master/README.md) + ## Custom Key and Certificate Files -You may find that you need a custom key and certificate file for https if you use multiple +You may find that you need a custom key and certificate file for HTTPS if you use multiple machines for development (or if your dev environment is containerized in Docker). -If you need to use a custom https setup, you can pass the `--https`, `--key-file` and -`--cert-file` flags to `npm run develop`. +If you need to use a custom HTTPS setup, you can pass the `--https`, `--key-file`, +`--cert-file`, and `--ca-file` flags to `npm run develop`. + +- `--cert-file` [relative/absolute path to ssl certificate file] +- `--key-file` [relative/absolute path to ssl key file] +- `--ca-file` [relative/absolute path to ssl certificate authority file] + +### Using `npm run develop` + +```shell +npm run develop -- --https --key-file ../relative/path/to/key.key --cert-file ../relative/path/to/cert.crt --ca-file ../relative/path/to/ca.crt +``` -- `--cert-file` [relative path to ssl certificate file] -- `--key-file` [relative path to ssl key file] +> Note: You can use relative or absolute paths with this command -See the example command: +### Using the Gatsby CLI ```shell -gatsby develop --https --key-file ../relative/path/to/key.key --cert-file ../relative/path/to/cert.crt +gatsby develop --https --key-file ../relative/path/to/key.key --cert-file ../relative/path/to/cert.crt --ca-file ../relative/path/to/ca.crt ``` -in most cases, the `--https` passed by itself is easier and more convenient to get local https. +> Note: You can use relative or absolute paths with this command + +### Flag usage + +Usage of the `--ca-file` flag is only required if your certificate is signed by a certificate authority. + +If your certificate is self-signed, then do not include the `--ca-file` flag. Also, if you want your browser to trust a self-signed certificate, you will need to add it to your operating system (or browser's, in Firefox's case) root certificate store for your browser to trust it. + +In most cases, the `--https` passed by itself is easier and more convenient to get local HTTPS. --- -Keep in mind that the automatic certificates issued with the `--https` flag are explicitly issued to `localhost` and will only be accepted there. Using it together with the `--host` option will likely result in browser warnings. +Automatic certificates issued with the `--https` flag are issued to `localhost` by default, unless you have used the `--host` flag. If you have, a record in your hosts file will automatically be configured to point the defined host to `127.0.0.1`. At this time, ip addresses defined by `--host` are not supported. diff --git a/packages/gatsby-cli/src/create-cli.js b/packages/gatsby-cli/src/create-cli.js index 35624f8625460..bbca1543a4795 100644 --- a/packages/gatsby-cli/src/create-cli.js +++ b/packages/gatsby-cli/src/create-cli.js @@ -137,13 +137,18 @@ function buildLocalCommands(cli, isLocalSite) { alias: `cert-file`, type: `string`, default: ``, - describe: `Custom HTTPS cert file (relative path; also required: --https, --key-file). See https://www.gatsbyjs.org/docs/local-https/`, + describe: `Custom HTTPS cert file (also required: --https, --key-file). See https://www.gatsbyjs.org/docs/local-https/`, }) .option(`k`, { alias: `key-file`, type: `string`, default: ``, - describe: `Custom HTTPS key file (relative path; also required: --https, --cert-file). See https://www.gatsbyjs.org/docs/local-https/`, + describe: `Custom HTTPS key file (also required: --https, --cert-file). See https://www.gatsbyjs.org/docs/local-https/`, + }) + .option(`ca-file`, { + type: `string`, + default: ``, + describe: `Custom HTTPS CA certificate file (also required: --https, --cert-file, --key-file). See https://www.gatsbyjs.org/docs/local-https/`, }) .option(`open-tracing-config-file`, { type: `string`, diff --git a/packages/gatsby/src/commands/develop.ts b/packages/gatsby/src/commands/develop.ts index 8b663ab633dd1..b5cc7978ca669 100644 --- a/packages/gatsby/src/commands/develop.ts +++ b/packages/gatsby/src/commands/develop.ts @@ -413,6 +413,7 @@ module.exports = async (program: IProgram): Promise => { program.ssl = await getSslCert({ name: sslHost, + caFile: program[`ca-file`], certFile: program[`cert-file`], keyFile: program[`key-file`], directory: program.directory, diff --git a/packages/gatsby/src/commands/types.ts b/packages/gatsby/src/commands/types.ts index e74117d3c3926..a4c86c68cadc4 100644 --- a/packages/gatsby/src/commands/types.ts +++ b/packages/gatsby/src/commands/types.ts @@ -1,8 +1,6 @@ import { PackageJson, Reporter } from "gatsby" export interface ICert { - keyPath: string - certPath: string key: string cert: string } diff --git a/packages/gatsby/src/utils/__tests__/__snapshots__/get-ssl-cert.js.snap b/packages/gatsby/src/utils/__tests__/__snapshots__/get-ssl-cert.js.snap index dab90a8f6eb24..bdbcac7331c83 100644 --- a/packages/gatsby/src/utils/__tests__/__snapshots__/get-ssl-cert.js.snap +++ b/packages/gatsby/src/utils/__tests__/__snapshots__/get-ssl-cert.js.snap @@ -1,20 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`gets ssl certs Custom SSL certificate loads a cert from a absolute paths 1`] = ` +exports[`gets ssl certs Custom SSL certificate loads a cert from absolute paths 1`] = ` Object { "cert": "/foo.crt::file", - "certPath": "/foo.crt", "key": "/foo.key::file", - "keyPath": "/foo.key", } `; exports[`gets ssl certs Custom SSL certificate loads a cert relative to a directory 1`] = ` Object { "cert": "/app/directory/foo.crt::file", - "certPath": "/app/directory/foo.crt", "key": "/app/directory/foo.key::file", - "keyPath": "/app/directory/foo.key", } `; @@ -57,7 +53,7 @@ Array [ exports[`gets ssl certs automatic SSL certificate sets up dev cert 1`] = ` Array [ Array [ - "setting up automatic SSL certificate (may require sudo) + "setting up automatic SSL certificate (may require elevated permissions/sudo) ", ], ] diff --git a/packages/gatsby/src/utils/__tests__/get-ssl-cert.js b/packages/gatsby/src/utils/__tests__/get-ssl-cert.js index 6e6c74158b434..3997ae64d4b0d 100644 --- a/packages/gatsby/src/utils/__tests__/get-ssl-cert.js +++ b/packages/gatsby/src/utils/__tests__/get-ssl-cert.js @@ -17,7 +17,7 @@ jest.mock(`devcert`, () => { } }) -const { certificateFor } = require(`devcert`) +const getDevCert = require(`devcert`).certificateFor const reporter = require(`gatsby-cli/lib/reporter`) const getSslCert = require(`../get-ssl-cert`) @@ -25,7 +25,7 @@ describe(`gets ssl certs`, () => { beforeEach(() => { reporter.panic.mockClear() reporter.info.mockClear() - certificateFor.mockClear() + getDevCert.mockClear() }) describe(`Custom SSL certificate`, () => { it.each([[{ certFile: `foo` }], [{ keyFile: `bar` }]])( @@ -46,7 +46,7 @@ describe(`gets ssl certs`, () => { }) ).resolves.toMatchSnapshot() }) - it(`loads a cert from a absolute paths`, () => { + it(`loads a cert from absolute paths`, () => { expect( getSslCert({ name: `mock-cert`, @@ -60,13 +60,17 @@ describe(`gets ssl certs`, () => { describe(`automatic SSL certificate`, () => { it(`sets up dev cert`, () => { getSslCert({ name: `mock-cert` }) - expect(certificateFor).toBeCalledWith(`mock-cert`, { - installCertutil: true, + expect(getDevCert).toBeCalledWith(`mock-cert`, { + getCaPath: true, + skipCertutilInstall: false, + ui: { + getWindowsEncryptionPassword: expect.any(Function), + }, }) expect(reporter.info.mock.calls).toMatchSnapshot() }) it(`panics if certificate can't be created`, () => { - certificateFor.mockImplementation(() => { + getDevCert.mockImplementation(() => { throw new Error(`mock error message`) }) getSslCert({ name: `mock-cert` }) diff --git a/packages/gatsby/src/utils/get-ssl-cert.js b/packages/gatsby/src/utils/get-ssl-cert.js index bf721e3a681ac..5e6b82c2f44fa 100644 --- a/packages/gatsby/src/utils/get-ssl-cert.js +++ b/packages/gatsby/src/utils/get-ssl-cert.js @@ -2,6 +2,7 @@ const report = require(`gatsby-cli/lib/reporter`) const fs = require(`fs`) const path = require(`path`) const os = require(`os`) +const prompts = require(`prompts`) const absoluteOrDirectory = (directory, filePath) => { // Support absolute paths @@ -11,7 +12,28 @@ const absoluteOrDirectory = (directory, filePath) => { return path.join(directory, filePath) } -module.exports = async ({ name, certFile, keyFile, directory }) => { +const getWindowsEncryptionPassword = async () => { + report.info( + [ + `A password is required to access the secure certificate authority key`, + `used for signing certificates.`, + ``, + `If this is the first time this has run, then this is to set the password`, + `for future use. If any new certificates are signed later, you will need`, + `to use this same password.`, + ``, + ].join(`\n`) + ) + const results = await prompts({ + type: `password`, + name: `value`, + message: `Please enter the CA password`, + validate: input => input.length > 0 || `You must enter a password.`, + }) + return results.value +} + +module.exports = async ({ name, certFile, keyFile, caFile, directory }) => { // check that cert file and key file are both true or both false, if they are both // false, it defaults to the automatic ssl if (certFile ? !keyFile : keyFile) { @@ -25,15 +47,18 @@ module.exports = async ({ name, certFile, keyFile, directory }) => { const keyPath = absoluteOrDirectory(directory, keyFile) const certPath = absoluteOrDirectory(directory, certFile) + process.env.NODE_EXTRA_CA_CERTS = caFile + ? absoluteOrDirectory(directory, caFile) + : certPath return await { - keyPath, - certPath, key: fs.readFileSync(keyPath), cert: fs.readFileSync(certPath), } } - report.info(`setting up automatic SSL certificate (may require sudo)\n`) + report.info( + `setting up automatic SSL certificate (may require elevated permissions/sudo)\n` + ) try { if ([`linux`, `darwin`].includes(os.platform()) && !process.env.HOME) { // this is a total hack to ensure process.env.HOME is set on linux and mac @@ -46,10 +71,21 @@ module.exports = async ({ name, certFile, keyFile, directory }) => { const mkdtemp = fs.mkdtempSync(path.join(os.tmpdir(), `home-`)) process.env.HOME = mkdtemp } - const certificateFor = require(`devcert`).certificateFor - return await certificateFor(name, { - installCertutil: true, + const getDevCert = require(`devcert`).certificateFor + const { caPath, key, cert } = await getDevCert(name, { + getCaPath: true, + skipCertutilInstall: false, + ui: { + getWindowsEncryptionPassword, + }, }) + if (caPath) { + process.env.NODE_EXTRA_CA_CERTS = caPath + } + return { + key, + cert, + } } catch (err) { report.panic({ id: `11522`, diff --git a/yarn.lock b/yarn.lock index 926f902340417..e6a99271e8708 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22685,6 +22685,11 @@ sudo-prompt@^8.2.0: resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.5.tgz#cc5ef3769a134bb94b24a631cc09628d4d53603e" integrity sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw== +sudo-prompt@^8.2.0: + version "8.2.5" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.5.tgz#cc5ef3769a134bb94b24a631cc09628d4d53603e" + integrity sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw== + supports-color@6.1.0, supports-color@^6.0.0, supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" @@ -24436,8 +24441,9 @@ vm-browserify@^1.0.1: integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== vue-template-compiler@^2.5.16: - version "2.5.17" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.17.tgz#52a4a078c327deb937482a509ae85c06f346c3cb" + version "2.6.10" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc" + integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg== dependencies: de-indent "^1.0.2" he "^1.1.0"