From 89748bc2016f6959c0d84855bcfe39fbaf4b61ee Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Thu, 17 Sep 2020 16:30:16 +0300 Subject: [PATCH 1/4] Make cacert:export save only the last certificate in the chain Signed-off-by: Mykola Morhun --- src/api/che.ts | 14 ++++++++++++++ src/commands/cacert/export.ts | 18 ++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/api/che.ts b/src/api/che.ts index 0f6bdb7ab..cc655f080 100644 --- a/src/api/che.ts +++ b/src/api/che.ts @@ -120,6 +120,20 @@ export class CheHelper { * If secret doesn't exist, undefined is returned. */ async retrieveCheCaCert(cheNamespace: string): Promise { + const cheCaSecretContent = await this.getCheSelfSignedSecretContent(cheNamespace) + if (!cheCaSecretContent) { + return + } + + const rootCertStartPos = cheCaSecretContent.lastIndexOf('-----BEGIN CERTIFICATE-----') + return cheCaSecretContent.substring(rootCertStartPos) + } + + /** + * Retrieves content of Che self-signed-certificate secret or undefined if the secret doesn't exist. + * Note, it contains certificate chain in pem format. + */ + private async getCheSelfSignedSecretContent(cheNamespace: string): Promise { const cheCaSecret = await this.kube.getSecret(CHE_ROOT_CA_SECRET_NAME, cheNamespace) if (!cheCaSecret) { return diff --git a/src/commands/cacert/export.ts b/src/commands/cacert/export.ts index 476793fbe..20faf4b01 100644 --- a/src/commands/cacert/export.ts +++ b/src/commands/cacert/export.ts @@ -11,15 +11,13 @@ import { Command, flags } from '@oclif/command' import { string } from '@oclif/parser/lib/flags' import * as fs from 'fs' -import * as Listr from 'listr' import * as os from 'os' import * as path from 'path' import { CheHelper } from '../../api/che' +import { KubeHelper } from '../../api/kube' import { cheNamespace, skipKubeHealthzCheck } from '../../common-flags' import { DEFAULT_CA_CERT_FILE_NAME } from '../../constants' -import { CheTasks } from '../../tasks/che' -import { ApiTasks } from '../../tasks/platforms/api' export default class Export extends Command { static description = 'Retrieves Eclipse Che self-signed certificate' @@ -41,17 +39,17 @@ export default class Export extends Command { async run() { const { flags } = this.parse(Export) - const ctx: any = {} + const kube = new KubeHelper(flags) const cheHelper = new CheHelper(flags) - const cheTasks = new CheTasks(flags) - const apiTasks = new ApiTasks() - const tasks = new Listr([], { renderer: 'silent' }) - tasks.add(apiTasks.testApiTasks(flags, this)) - tasks.add(cheTasks.verifyCheNamespaceExistsTask(flags, this)) + if (!await kube.hasReadPermissionsForNamespace(flags.chenamespace)) { + throw new Error('E_PERM_DENIED - Permission for Che server namespace are required') + } + if (!await cheHelper.cheNamespaceExist(flags.chenamespace)) { + throw new Error(`E_BAD_NS - Namespace ${flags.chenamespace} does not exist. Please specify it with --chenamespace flag`) + } try { - await tasks.run(ctx) const cheCaCert = await cheHelper.retrieveCheCaCert(flags.chenamespace) if (cheCaCert) { const targetFile = await cheHelper.saveCheCaCert(cheCaCert, this.getTargetFile(flags.destination)) From 974bf5dd818f483bfc1aa4604bf8403dc139e838 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Fri, 18 Sep 2020 15:01:49 +0300 Subject: [PATCH 2/4] Parse certs to get only CA ones Signed-off-by: Mykola Morhun --- package.json | 2 ++ src/api/che.ts | 20 ++++++++++++++++++-- src/commands/cacert/export.ts | 4 ++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ccef778ca..1339bb680 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "lodash": "^4.17.20", "mkdirp": "^1.0.4", "node-notifier": "^8.0.0", + "node-forge": "^0.10.0", "stream-buffers": "^3.0.2", "tslib": "^1" }, @@ -51,6 +52,7 @@ "@types/js-yaml": "^3.12.5", "@types/listr": "^0.14.2", "@types/node": "^12", + "@types/node-forge": "^0.9.5", "chai": "^4.2.0", "cpx": "^1.5.0", "globby": "^11", diff --git a/src/api/che.ts b/src/api/che.ts index cc655f080..13b77f033 100644 --- a/src/api/che.ts +++ b/src/api/che.ts @@ -16,6 +16,7 @@ import * as commandExists from 'command-exists' import * as fs from 'fs-extra' import * as https from 'https' import * as yaml from 'js-yaml' +import * as nodeforge from 'node-forge' import * as os from 'os' import * as path from 'path' @@ -125,8 +126,23 @@ export class CheHelper { return } - const rootCertStartPos = cheCaSecretContent.lastIndexOf('-----BEGIN CERTIFICATE-----') - return cheCaSecretContent.substring(rootCertStartPos) + const pemBeginHeader = '-----BEGIN CERTIFICATE-----' + const pemEndHeader = '-----END CERTIFICATE-----' + const certRegExp = new RegExp(`(^${pemBeginHeader}$(?:(?!${pemBeginHeader}).)*^${pemEndHeader}$)`, 'mgs') + const certsPem = cheCaSecretContent.match(certRegExp) + + const caCertsPem: string[] = [] + if (certsPem) { + for (const certPem of certsPem) { + const cert = nodeforge.pki.certificateFromPem(certPem) + const basicConstraintsExt = cert.getExtension('basicConstraints') + if (basicConstraintsExt && (basicConstraintsExt as any).cA) { + caCertsPem.push(certPem) + } + } + } + + return caCertsPem.join('\n') } /** diff --git a/src/commands/cacert/export.ts b/src/commands/cacert/export.ts index 20faf4b01..372b9d29b 100644 --- a/src/commands/cacert/export.ts +++ b/src/commands/cacert/export.ts @@ -43,7 +43,7 @@ export default class Export extends Command { const cheHelper = new CheHelper(flags) if (!await kube.hasReadPermissionsForNamespace(flags.chenamespace)) { - throw new Error('E_PERM_DENIED - Permission for Che server namespace are required') + throw new Error(`E_PERM_DENIED - Permission for Che server namespace "${flags.chenamespace}" are required`) } if (!await cheHelper.cheNamespaceExist(flags.chenamespace)) { throw new Error(`E_BAD_NS - Namespace ${flags.chenamespace} does not exist. Please specify it with --chenamespace flag`) @@ -55,7 +55,7 @@ export default class Export extends Command { const targetFile = await cheHelper.saveCheCaCert(cheCaCert, this.getTargetFile(flags.destination)) this.log(`Eclipse Che self-signed CA certificate is exported to ${targetFile}`) } else { - this.log('Seems commonly trusted certificate is used.') + this.log('Self signed certificate secret not found. Is commonly trusted certificate used?') } } catch (error) { this.error(error) From a151f029162c0a7fdf586cb1c7161aa1684c8275 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Fri, 18 Sep 2020 16:20:05 +0300 Subject: [PATCH 3/4] Fixes Signed-off-by: Mykola Morhun --- src/commands/cacert/export.ts | 2 +- src/tasks/che.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/cacert/export.ts b/src/commands/cacert/export.ts index 372b9d29b..68e58fa62 100644 --- a/src/commands/cacert/export.ts +++ b/src/commands/cacert/export.ts @@ -43,7 +43,7 @@ export default class Export extends Command { const cheHelper = new CheHelper(flags) if (!await kube.hasReadPermissionsForNamespace(flags.chenamespace)) { - throw new Error(`E_PERM_DENIED - Permission for Che server namespace "${flags.chenamespace}" are required`) + throw new Error(`E_PERM_DENIED - Permission denied: no read access to '${flags.chenamespace}' namespace`) } if (!await cheHelper.cheNamespaceExist(flags.chenamespace)) { throw new Error(`E_BAD_NS - Namespace ${flags.chenamespace} does not exist. Please specify it with --chenamespace flag`) diff --git a/src/tasks/che.ts b/src/tasks/che.ts index ab6e324d2..0d60114c1 100644 --- a/src/tasks/che.ts +++ b/src/tasks/che.ts @@ -493,7 +493,7 @@ export class CheTasks { title: `Verify if namespace '${flags.chenamespace}' exists`, task: async () => { if (!await this.che.cheNamespaceExist(flags.chenamespace)) { - command.error(`E_BAD_NS - Namespace does not exist.\nThe Kubernetes Namespace "${flags.chenamespace}" doesn't exist. The configuration cannot be injected.\nFix with: verify the namespace where workspace is running (kubectl get --all-namespaces deployment | grep workspace)`, { code: 'EBADNS' }) + command.error(`E_BAD_NS - Namespace does not exist.\nThe Kubernetes Namespace "${flags.chenamespace}" doesn't exist.`, { code: 'EBADNS' }) } } }] From 20dae9ae9c10a9de58f153242968d6d4b59a36fb Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Mon, 21 Sep 2020 09:20:11 +0300 Subject: [PATCH 4/4] Update yarn.lock Signed-off-by: Mykola Morhun --- package.json | 2 +- yarn.lock | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1339bb680..81dcbd09d 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,8 @@ "listr-verbose-renderer": "^0.6.0", "lodash": "^4.17.20", "mkdirp": "^1.0.4", - "node-notifier": "^8.0.0", "node-forge": "^0.10.0", + "node-notifier": "^8.0.0", "stream-buffers": "^3.0.2", "tslib": "^1" }, diff --git a/yarn.lock b/yarn.lock index f49d72039..b7a4aa7b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -867,6 +867,13 @@ dependencies: "@types/node" "*" +"@types/node-forge@^0.9.5": + version "0.9.5" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-0.9.5.tgz#648231d79da197216290429020698d4e767365a0" + integrity sha512-rrN3xfA/oZIzwOnO3d2wRQz7UdeVkmMMPjWUCfpPTPuKFVb3D6G10LuiVHYYmvrivBBLMx4m0P/FICoDbNZUMA== + dependencies: + "@types/node" "*" + "@types/node-notifier@^8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@types/node-notifier/-/node-notifier-8.0.0.tgz#51100d67155ed1500a8aaa633987109f59a0637d" @@ -4256,6 +4263,11 @@ nock@^11.7.0: mkdirp "^0.5.0" propagate "^2.0.0" +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + node-forge@^0.8.5: version "0.8.5" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.5.tgz#57906f07614dc72762c84cef442f427c0e1b86ee"