From 884028b58a16dd486e982f67dd83458b30f3c355 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Wed, 25 Mar 2020 12:58:34 +0200 Subject: [PATCH] feat: Add basic TLS configuration checks (#599) * Add basic TLS configuration checks Signed-off-by: Mykola Morhun --- README.md | 11 +++- src/commands/server/start.ts | 9 ++- src/tasks/installers/operator.ts | 101 ++++++++++++++++++++++++++++++- src/util.ts | 8 +++ 4 files changed, 121 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c04b552a7..a9b631c4e 100644 --- a/README.md +++ b/README.md @@ -274,10 +274,15 @@ OPTIONS -s, --tls Enable TLS encryption. - Note, that this option is turned on by default for kubernetes infrastructure. - If it is needed to provide own certificate, 'che-tls' secret with TLS certificate must be - created in the configured namespace. Otherwise, it will be automatically generated. + Note, this option is turned on by default. + For Kubernetes infrastructure, it is required to provide own certificate: 'che-tls' secret with + TLS certificate must be pre-created in the configured namespace. + The only exception is Helm installer. In that case the secret will be generated automatically. For OpenShift, router will use default cluster certificates. + If the certificate is self-signed, '--self-signed-cert' option should be provided, otherwise + Che won't be able to start. + Please see docs for more details: + https://www.eclipse.org/che/docs/che-7/setup-che-in-tls-mode-with-self-signed-certificate/ -t, --templates=templates [default: templates] Path to the templates folder diff --git a/src/commands/server/start.ts b/src/commands/server/start.ts index 70e6cb8e5..d2e77f34a 100644 --- a/src/commands/server/start.ts +++ b/src/commands/server/start.ts @@ -74,9 +74,12 @@ export default class Start extends Command { tls: flags.boolean({ char: 's', description: `Enable TLS encryption. - Note, that this option is turned on by default for kubernetes infrastructure. - If it is needed to provide own certificate, 'che-tls' secret with TLS certificate must be created in the configured namespace. Otherwise, it will be automatically generated. - For OpenShift, router will use default cluster certificates.` + Note, this option is turned on by default. + For Kubernetes infrastructure, it is required to provide own certificate: 'che-tls' secret with TLS certificate must be pre-created in the configured namespace. + The only exception is Helm installer. In that case the secret will be generated automatically. + For OpenShift, router will use default cluster certificates. + If the certificate is self-signed, '--self-signed-cert' option should be provided, otherwise Che won't be able to start. + Please see docs for more details: https://www.eclipse.org/che/docs/che-7/setup-che-in-tls-mode-with-self-signed-certificate/` }), 'self-signed-cert': flags.boolean({ description: `Authorize usage of self signed certificates for encryption. diff --git a/src/tasks/installers/operator.ts b/src/tasks/installers/operator.ts index 99ae78f31..309887468 100644 --- a/src/tasks/installers/operator.ts +++ b/src/tasks/installers/operator.ts @@ -11,7 +11,7 @@ import { V1Deployment } from '@kubernetes/client-node' import { Command } from '@oclif/command' import { cli } from 'cli-ux' import * as execa from 'execa' -import { readFileSync } from 'fs' +import * as fs from 'fs' import { copy, mkdirp, remove } from 'fs-extra' import * as yaml from 'js-yaml' import * as Listr from 'listr' @@ -19,6 +19,7 @@ import * as path from 'path' import { CheHelper } from '../../api/che' import { KubeHelper } from '../../api/kube' +import { isKubernetesPlatformFamily } from '../../util' export class OperatorTasks { operatorServiceAccount = 'che-operator' @@ -60,6 +61,68 @@ export class OperatorTasks { } } }, + { + title: 'Checking for pre-created TLS secret', + // In case of Openshift infrastructure the certificate from cluster router will be used, so no need in the `che-tls` secret. + skip: () => !isKubernetesPlatformFamily(flags.platform), + task: async (_: any, task: any) => { + // Che is being deployed on Kubernetes infrastructure + + if (! await this.checkTlsMode(flags)) { + // No TLS mode, skip this check + return + } + + const cheSecretName = 'che-tls' + const cheSecret = await kube.getSecret(cheSecretName, flags.chenamespace) + if (cheSecret) { + task.title = `${task.title}... "${cheSecretName}" secret found` + return + } + + // The secret is required but doesn't exist, show error message. + const errorMessage = + `Che TLS mode is turned on, but required "${cheSecretName}" secret is not pre-created in "${flags.chenamespace}" namespace, so Eclipse Che cannot be started. \n` + + 'This is not bug in Eclipse Che and such behavior is expected. \n' + + 'Please refer to Che documentation for more informations: ' + + 'https://www.eclipse.org/che/docs/che-7/setup-che-in-tls-mode-with-self-signed-certificate/' + throw new Error(errorMessage) + } + }, + { + title: 'Checking certificate', + // If the flag is set no need to check if it is required + skip: () => flags['self-signed-cert'], + task: async (_: any, task: any) => { + if (! await this.checkTlsMode(flags)) { + // No TLS mode, skip this check + return + } + + const warningMessage = 'Self-signed certificate is used, so "--self-signed-cert" option is required. Added automatically.' + + const platform = flags.platform + if (platform === 'minikube' || platform === 'crc' || platform === 'minishift') { + // There is no way to use real certificate on listed above platforms + cli.warn(warningMessage) + flags['self-signed-cert'] = true + task.title = `${task.title}... self-signed` + return + } + + if (flags.domain && (flags.domain.endsWith('nip.io') || flags.domain.endsWith('xip.io'))) { + // It is not possible to use real certificate with *.nip.io and similar services + cli.warn(warningMessage) + flags['self-signed-cert'] = true + task.title = `${task.title}... self-signed` + return + } + + // TODO check the secret certificate if it is commonly trusted. + cli.info('TLS mode is turned on, however we failed to determine whether self-signed certificate is used. \n\ + Please rerun chectl with "--self-signed-cert" option if it is the case, otherwise Eclipse Che will fail to start.') + } + }, { title: `Create ServiceAccount ${this.operatorServiceAccount} in namespace ${flags.chenamespace}`, task: async (_ctx: any, task: any) => { @@ -471,7 +534,7 @@ export class OperatorTasks { return flags['che-operator-image'] } else { const filePath = flags.templates + '/che-operator/operator.yaml' - const yamlFile = readFileSync(filePath) + const yamlFile = fs.readFileSync(filePath) const yamlDeployment = yaml.safeLoad(yamlFile.toString()) as V1Deployment return yamlDeployment.spec!.template.spec!.containers[0].image! } @@ -497,4 +560,38 @@ export class OperatorTasks { return container.image } + + /** + * Checks if TLS is disabled via operator custom resource. + * Returns true if TLS is enabled (or omitted) and false if it is explicitly disabled. + */ + private async checkTlsMode(flags: any): Promise { + if (flags['che-operator-cr-yaml']) { + const cheOperatorCrYamlPath = flags['che-operator-cr-yaml'] + if (fs.existsSync(cheOperatorCrYamlPath)) { + const cr = yaml.safeLoad(fs.readFileSync(cheOperatorCrYamlPath).toString()) + if (cr && cr.spec && cr.spec.server && cr.spec.server.tlsSupport === false) { + return false + } + } + } + + if (flags['che-operator-cr-patch-yaml']) { + const cheOperatorCrPatchYamlPath = flags['che-operator-cr-patch-yaml'] + if (fs.existsSync(cheOperatorCrPatchYamlPath)) { + const crPatch = yaml.safeLoad(fs.readFileSync(cheOperatorCrPatchYamlPath).toString()) + if (crPatch && crPatch.spec && crPatch.spec.server && crPatch.spec.server.tlsSupport === false) { + return false + } + } + } + + // If tls flag is undefined we suppose that tls is turned on + if (flags.tls === false) { + return false + } + + // TLS is on + return true + } } diff --git a/src/util.ts b/src/util.ts index 74af9ccc5..39a5fd639 100644 --- a/src/util.ts +++ b/src/util.ts @@ -23,3 +23,11 @@ export function getClusterClientCommand(): string { throw new Error('No cluster CLI client is installed.') } + +export function isKubernetesPlatformFamily(platform: string): boolean { + return platform === 'k8s' || platform === 'minikube' || platform === 'microk8s' +} + +export function isOpenshiftPlatformFamily(platform: string): boolean { + return platform === 'openshift' || platform === 'minishift' || platform === 'crc' +}