From 40cca32b64389574fb3b120d3f761857d47f0d18 Mon Sep 17 00:00:00 2001 From: Mike North Date: Tue, 11 Feb 2020 09:57:58 -0800 Subject: [PATCH] fix: correct certificate path and post-placement command for RHEL7 --- package.json | 1 + src/errors.ts | 5 ++ src/platforms/linux.ts | 121 ++++++++++++++++++++++++++++++++++++----- yarn.lock | 12 ++++ 4 files changed, 125 insertions(+), 14 deletions(-) create mode 100644 src/errors.ts diff --git a/package.json b/package.json index 37511bd..51985c9 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@types/mkdirp": "^0.5.2", "@types/node": "^8.5.7", "@types/rimraf": "^2.0.2", + "@types/systeminformation": "^3.54.1", "@types/tmp": "^0.0.33", "@typescript-eslint/eslint-plugin": "^2.17.0", "@typescript-eslint/parser": "^2.17.0", diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..15cb47d --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,5 @@ +export class UnreachableError extends Error { + constructor(nvr: never, message: string) { + super(`You have encountered a situation that was thought to be impossible\n${message}\nThis value should have been a "never": ${nvr}`) + } +} diff --git a/src/platforms/linux.ts b/src/platforms/linux.ts index b6e95d6..b44aa86 100644 --- a/src/platforms/linux.ts +++ b/src/platforms/linux.ts @@ -2,7 +2,8 @@ import * as path from "path"; import { existsSync as exists, readFileSync as read, - writeFileSync as writeFile + writeFileSync as writeFile, + existsSync } from "fs"; import * as createDebug from "debug"; import { sync as commandExists } from "command-exists"; @@ -18,9 +19,77 @@ import { run } from "../utils"; import { Options } from "../index"; import UI from "../user-interface"; import { Platform } from "."; +import * as si from 'systeminformation'; +import { UnreachableError } from "../errors"; const debug = createDebug("devcert:platforms:linux"); +enum LinuxFlavor { + Unknown = 0, + Ubuntu, + Rhel7, + Fedora, +} + +async function determineLinuxFlavor(distroPromise: Promise = si.osInfo().then(info => info.distro)): Promise<{ flav: LinuxFlavor, message?: string }> { + const distro = await distroPromise; + switch (distro) { + case 'Red Hat Enterprise Linux Workstation': + return { flav: LinuxFlavor.Rhel7 }; + case 'Ubuntu': + return { flav: LinuxFlavor.Ubuntu }; + case 'Fedora': + return { flav: LinuxFlavor.Fedora }; + default: + return { flav: LinuxFlavor.Unknown, message: `Unknown linux distro: ${distro}` }; + } +} + +interface Cmd { + command: string; + args: string[] +} + +interface LinuxFlavorDetails { + caFolders: string[]; + postCaPlacementCommands: Cmd[] + postCaRemovalCommands: Cmd[] +} + +function linuxFlavorDetails(flavor: Exclude): LinuxFlavorDetails { + switch (flavor) { + case LinuxFlavor.Rhel7: + case LinuxFlavor.Fedora: + return { + caFolders: ['/etc/pki/ca-trust/source/anchors', '/usr/share/pki/ca-trust-source'], + postCaPlacementCommands: [{ + command: 'sudo', args: ['update-ca-trust'] + }], + postCaRemovalCommands: [{ + command: 'sudo', args: ['update-ca-trust'] + }] + }; + case LinuxFlavor.Ubuntu: + return { + caFolders: ['/etc/pki/ca-trust/source/anchors', '/usr/local/share/ca-certificates'], + postCaPlacementCommands: [{ + command: 'sudo', args: ['update-ca-certificates'] + }], + postCaRemovalCommands: [{ + command: 'sudo', args: ['update-ca-certificates'] + }] + }; + + default: + throw new UnreachableError(flavor, 'Unable to detect linux flavor'); + } +} +async function currentLinuxFlavorDetails(): Promise { + const { flav: flavor, message } = await determineLinuxFlavor(); + if (!flavor) throw new Error(message); // TODO better error + return linuxFlavorDetails(flavor); +} + export default class LinuxPlatform implements Platform { private FIREFOX_NSS_DIR = path.join(HOME, ".mozilla/firefox/*"); private CHROME_NSS_DIR = path.join(HOME, ".pki/nssdb"); @@ -44,11 +113,17 @@ export default class LinuxPlatform implements Platform { ): Promise { debug("Adding devcert root CA to Linux system-wide trust stores"); // run(`sudo cp ${ certificatePath } /etc/ssl/certs/devcert.crt`); - run( - `sudo cp "${certificatePath}" /usr/local/share/ca-certificates/devcert.crt` - ); + const linuxInfo = await currentLinuxFlavorDetails(); + const { caFolders, postCaPlacementCommands } = linuxInfo; + caFolders.forEach(folder => { + run( + `sudo cp "${certificatePath}" ${path.join(folder, 'devcert.crt')}` + ); + }) // run(`sudo bash -c "cat ${ certificatePath } >> /etc/ssl/certs/ca-certificates.crt"`); - run(`sudo update-ca-certificates`); + postCaPlacementCommands.forEach(({ command, args }) => { + run(`${command} ${args.join(' ')}`.trim()); + }) if (this.isFirefoxInstalled()) { // Firefox @@ -104,15 +179,33 @@ export default class LinuxPlatform implements Platform { } } - removeFromTrustStores(certificatePath: string): void { - try { - run(`sudo rm /usr/local/share/ca-certificates/devcert.crt`); - run(`sudo update-ca-certificates`); - } catch (e) { - debug( - `failed to remove ${certificatePath} from /usr/local/share/ca-certificates, continuing. ${e.toString()}` - ); - } + async removeFromTrustStores(certificatePath: string): Promise { + const linuxInfo = await currentLinuxFlavorDetails(); + const { caFolders, postCaRemovalCommands } = linuxInfo; + caFolders.forEach(folder => { + const certPath = path.join(folder, 'devcert.crt'); + try { + const exists = existsSync(certPath); + debug({ exists }) + if (!exists) { + debug(`cert at location ${certPath} was not found. Skipping...`) + return; + } else { + run( + `sudo rm "${certificatePath}" ${certPath}` + ); + postCaRemovalCommands.forEach(({ command, args }) => { + run(`${command} ${args.join(' ')}`.trim()); + }) + } + } catch (e) { + debug( + `failed to remove ${certificatePath} from ${certPath}, continuing. ${e.toString()}` + ); + } + }) + // run(`sudo bash -c "cat ${ certificatePath } >> /etc/ssl/certs/ca-certificates.crt"`); + if (commandExists("certutil")) { if (this.isFirefoxInstalled()) { removeCertificateFromNSSCertDB( diff --git a/yarn.lock b/yarn.lock index b9637a9..19af4c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -102,6 +102,13 @@ "@types/glob" "*" "@types/node" "*" +"@types/systeminformation@^3.54.1": + version "3.54.1" + resolved "https://registry.yarnpkg.com/@types/systeminformation/-/systeminformation-3.54.1.tgz#bbc1b9e4ae2e60d45f75906029c6fb5436e1c5cb" + integrity sha512-vvisj2mdWygyc0jk/5XtSVq9gtxCmF3nrGwv8wVway8pwNRhtPji/MU9dc1L0F6rl0F/NFIHa4ScRU7wmNaHmg== + dependencies: + systeminformation "*" + "@types/tmp@^0.0.33": version "0.0.33" resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d" @@ -2169,6 +2176,11 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +systeminformation@*: + version "4.21.2" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.21.2.tgz#0ee92c0565687a5301ba141444643b43d0370ae2" + integrity sha512-7O6laxHTstfj9MSrX77lKLfWz0zqqutWL0+uoJkR7sOOr4XCTwD0QG4shUVCiOWlVX+a8/gHJUio420FrSk8/w== + table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"