Skip to content

Commit

Permalink
feat: Use TLS secrets autogeneration mechanism in chectl (#679)
Browse files Browse the repository at this point in the history
* Use TLS secrets autogeneration mechanism in chectl

Signed-off-by: Mykola Morhun <mmorhun@redhat.com>
  • Loading branch information
mmorhun authored Apr 30, 2020
1 parent bc607f3 commit 7e99d04
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 140 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ USAGE
OPTIONS
-a, --installer=helm|operator|olm|minishift-addon
Installer type
[default: operator] Installer type
-b, --domain=domain
Domain of the Kubernetes cluster (e.g. example.k8s-cluster.com or <local-ip>.nip.io)
Expand Down
19 changes: 17 additions & 2 deletions src/api/che.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ 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 os from 'os'
import * as path from 'path'

import { OpenShiftHelper } from '../api/openshift'
import { CHE_ROOT_CA_SECRET_NAME } from '../constants'
import { CHE_ROOT_CA_SECRET_NAME, DEFAULT_CA_CERT_FILE_NAME } from '../constants'

import { Devfile } from './devfile'
import { KubeHelper } from './kube'
Expand Down Expand Up @@ -111,7 +112,7 @@ export class CheHelper {
/**
* Gets self-signed Che CA certificate from 'self-signed-certificate' secret. The secret should exist.
*/
async retrieveEclipseCheCaCert(cheNamespace: string): Promise<string> {
async retrieveCheCaCert(cheNamespace: string): Promise<string> {
const cheCaSecret = await this.kube.getSecret(CHE_ROOT_CA_SECRET_NAME, cheNamespace)
if (!cheCaSecret) {
throw new Error('Che CA self-signed certificate not found. Are you using self-signed certificate?')
Expand All @@ -124,6 +125,20 @@ export class CheHelper {
throw new Error(`Secret "${CHE_ROOT_CA_SECRET_NAME}" has invalid format: "ca.crt" key not found in data.`)
}

async saveCheCaCert(cheCaCert: string, destinaton?: string): Promise<string> {
if (destinaton && fs.existsSync(destinaton)) {
if (fs.lstatSync(destinaton).isDirectory()) {
destinaton = path.join(destinaton, DEFAULT_CA_CERT_FILE_NAME)
}
} else {
// Fallback to default location
destinaton = path.join(os.homedir(), DEFAULT_CA_CERT_FILE_NAME)
}

fs.writeFileSync(destinaton, cheCaCert)
return destinaton
}

async cheK8sURL(namespace = ''): Promise<string> {
const ingress_names = ['che', 'che-ingress']
for (const ingress_name of ingress_names) {
Expand Down
13 changes: 5 additions & 8 deletions src/commands/cacert/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ import * as path from 'path'

import { CheHelper } from '../../api/che'
import { cheNamespace } from '../../common-flags'
import { DEFAULT_CA_CERT_FILE_NAME } from '../../constants'
import { CheTasks } from '../../tasks/che'
import { ApiTasks } from '../../tasks/platforms/api'
import { PlatformTasks } from '../../tasks/platforms/platform'

const DEFAULT_CA_CERT_FILE_NAME = 'cheCA.crt'

export default class Export extends Command {
static description = 'Retrieves Eclipse Che self-signed certificate'

Expand All @@ -36,7 +35,7 @@ export default class Export extends Command {
}),
destination: string({
char: 'd',
description: `Destination where to store Che CA certificate.
description: `Destination where to store Che self-signed CA certificate.
If the destination is a file (might not exist), then the certificate will be saved there in PEM format.
If the destination is a directory, then ${DEFAULT_CA_CERT_FILE_NAME} file will be created there with Che certificate in PEM format.
If this option is ommited, then Che certificate will be stored in user's home directory as ${DEFAULT_CA_CERT_FILE_NAME}`,
Expand All @@ -54,16 +53,14 @@ export default class Export extends Command {
const apiTasks = new ApiTasks()
const tasks = new Listr([], { renderer: 'silent' })

const targetFile = this.getTargetFile(flags.destination)

tasks.add(platformTasks.preflightCheckTasks(flags, this))
tasks.add(apiTasks.testApiTasks(flags, this))
tasks.add(cheTasks.verifyCheNamespaceExistsTask(flags, this))

try {
await tasks.run(ctx)
const cheCaCert = await cheHelper.retrieveEclipseCheCaCert(flags.chenamespace)
fs.writeFileSync(targetFile, cheCaCert)
const cheCaCert = await cheHelper.retrieveCheCaCert(flags.chenamespace)
const targetFile = await cheHelper.saveCheCaCert(cheCaCert, this.getTargetFile(flags.destination))
this.log(`Eclipse Che self-signed CA certificate is exported to ${targetFile}`)
} catch (error) {
this.error(error)
Expand All @@ -82,7 +79,7 @@ export default class Export extends Command {
return fs.lstatSync(destinaton).isDirectory() ? path.join(destinaton, DEFAULT_CA_CERT_FILE_NAME) : destinaton
}

this.error(`Given path "${destinaton}" doesn\'t exist.`)
this.error(`Given path "${destinaton}" doesn't exist.`)
}

}
78 changes: 37 additions & 41 deletions src/commands/server/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
import { Command, flags } from '@oclif/command'
import { boolean, string } from '@oclif/parser/lib/flags'
import * as fs from 'fs-extra'
import * as yaml from 'js-yaml'
import * as Listr from 'listr'
import * as notifier from 'node-notifier'
import * as os from 'os'
import * as path from 'path'

import { cheDeployment, cheNamespace, listrRenderer } from '../../common-flags'
import { DEFAULT_CHE_IMAGE, DEFAULT_CHE_OPERATOR_IMAGE } from '../../constants'
import { DEFAULT_CHE_IMAGE, DEFAULT_CHE_OPERATOR_IMAGE, DOCS_LINK_INSTALL_TLS_WITH_SELF_SIGNED_CERT } from '../../constants'
import { CheTasks } from '../../tasks/che'
import { retrieveCheCaCertificateTask } from '../../tasks/installers/common-tasks'
import { InstallerTasks } from '../../tasks/installers/installer'
import { ApiTasks } from '../../tasks/platforms/api'
import { PlatformTasks } from '../../tasks/platforms/platform'
Expand Down Expand Up @@ -79,7 +81,7 @@ export default class Start extends Command {
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/installing-che-in-tls-mode-with-self-signed-certificates/`
Please see docs for more details: ${DOCS_LINK_INSTALL_TLS_WITH_SELF_SIGNED_CERT}`
}),
'self-signed-cert': flags.boolean({
description: `Authorize usage of self signed certificates for encryption.
Expand All @@ -96,7 +98,7 @@ export default class Start extends Command {
char: 'a',
description: 'Installer type',
options: ['helm', 'operator', 'olm', 'minishift-addon'],
default: ''
default: 'operator'
}),
domain: string({
char: 'b',
Expand Down Expand Up @@ -186,43 +188,8 @@ export default class Start extends Command {
})
}

setPlaformDefaults(flags: any) {
if (flags.platform === 'minishift') {
if (!flags.multiuser && flags.installer === '') {
flags.installer = 'minishift-addon'
}
if (flags.multiuser && flags.installer === '') {
flags.installer = 'operator'
}
} else if (flags.platform === 'minikube') {
if (!flags.multiuser && flags.installer === '') {
flags.installer = 'helm'
}
if (flags.multiuser && flags.installer === '') {
flags.installer = 'operator'
}
} else if (flags.platform === 'openshift') {
if (flags.installer === '') {
flags.installer = 'operator'
}
} else if (flags.platform === 'k8s') {
if (flags.installer === '') {
flags.installer = 'helm'
}
} else if (flags.platform === 'docker-desktop') {
if (flags.installer === '') {
flags.installer = 'helm'
}
} else if (flags.platform === 'crc') {
if (flags.installer === '') {
flags.installer = 'operator'
}
}

// TODO when tls by default is implemented for all platforms, make `tls` flag turned on by default.
if (flags.installer === 'helm' && (flags.platform === 'k8s' || flags.platform === 'minikube' || flags.platform === 'microk8s')) {
flags.tls = true
}
async setPlaformDefaults(flags: any): Promise<void> {
flags.tls = await this.checkTlsMode(flags)

if (!flags.templates) {
// use local templates folder if present
Expand All @@ -244,6 +211,34 @@ export default class Start extends Command {
}
}

/**
* Checks if TLS is disabled via operator custom resource.
* Returns true if TLS is enabled (or omitted) and false if it is explicitly disabled.
*/
async checkTlsMode(flags: any): Promise<boolean> {
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
}
}
}

return true
}

checkPlatformCompatibility(flags: any) {
if (flags.installer === 'operator' && flags['che-operator-cr-yaml']) {
const ignoredFlags = []
Expand Down Expand Up @@ -341,7 +336,7 @@ export default class Start extends Command {
task: () => new Listr(cheTasks.checkIfCheIsInstalledTasks(flags, this))
})

this.setPlaformDefaults(flags)
await this.setPlaformDefaults(flags)
let installTasks = new Listr(installerTasks.installTasks(flags, this), listrOptions)

const startDeployedCheTasks = new Listr([{
Expand All @@ -355,6 +350,7 @@ export default class Start extends Command {
title: '✅ Post installation checklist',
task: () => new Listr(cheTasks.waitDeployedChe(flags, this))
},
retrieveCheCaCertificateTask(flags),
{
title: 'Show important messages',
enabled: ctx => ctx.highlightedMessages.length > 0,
Expand Down
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ export const CA_CERT_GENERATION_JOB_IMAGE = 'quay.io/eclipse/che-cert-manager-ca
export const CERT_MANAGER_NAMESPACE_NAME = 'cert-manager'
export const CHE_TLS_SECRET_NAME = 'che-tls'
export const CHE_ROOT_CA_SECRET_NAME = 'self-signed-certificate'
export const DEFAULT_CA_CERT_FILE_NAME = 'cheCA.crt'

export const operatorCheCluster = 'eclipse-che'
export const CHE_CLUSTER_CR_NAME = 'eclipse-che'

export const defaultOpenshiftMarketPlaceNamespace = 'openshift-marketplace'
export const defaultOLMKubernetesNamespace = 'olm'

// Documentation links
export const DOCS_LINK_INSTALL_TLS_WITH_SELF_SIGNED_CERT = 'https://www.eclipse.org/che/docs/che-7/installing-che-in-tls-mode-with-self-signed-certificates/'
export const DOCS_LINK_IMPORT_CA_CERT_INTO_BROWSER = 'https://www.eclipse.org/che/docs/che-7/installing-che-in-tls-mode-with-self-signed-certificates/#using-che-with-tls_installing-che-in-tls-mode-with-self-signed-certificates'
export const DOCS_LINK_AUTH_TO_CHE_SERVER_VIA_OPENID = ' https://www.eclipse.org/che/docs/che-7/authenticating-users/#authenticating-to-the-che-server-using-openid_authenticating-to-the-che-server'
4 changes: 3 additions & 1 deletion src/tasks/che.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as Listr from 'listr'
import { CheHelper } from '../api/che'
import { KubeHelper } from '../api/kube'
import { OpenShiftHelper } from '../api/openshift'
import { DOCS_LINK_AUTH_TO_CHE_SERVER_VIA_OPENID } from '../constants'

import { KubeTasks } from './kube'

Expand Down Expand Up @@ -255,7 +256,8 @@ export class CheTasks {
enabled: (ctx: any) => !ctx.isCheStopped,
task: async (ctx: any, task: any) => {
if (ctx.isAuthEnabled && !this.cheAccessToken) {
command.error('E_AUTH_REQUIRED - Eclipse Che authentication is enabled and an access token need to be provided (flag --access-token).\nFor instructions to retrieve a valid access token refer to https://www.eclipse.org/che/docs/pages/che-7/administration-guide/proc_authenticating-to-the-che-server-using-openid.html')
command.error('E_AUTH_REQUIRED - Eclipse Che authentication is enabled and an access token need to be provided (flag --access-token). ' +
`For instructions to retrieve a valid access token refer to ${DOCS_LINK_AUTH_TO_CHE_SERVER_VIA_OPENID}`)
}
try {
const cheURL = await this.che.cheURL(this.cheNamespace)
Expand Down
20 changes: 9 additions & 11 deletions src/tasks/component-installers/cert-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/

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 { CA_CERT_GENERATION_JOB_IMAGE, CERT_MANAGER_NAMESPACE_NAME, CHE_TLS_SECRET_NAME } from '../../constants'
import { getMessageImportCaCertIntoBrowser } from '../installers/common-tasks'

export const CERT_MANAGER_CA_SECRET_NAME = 'ca'

export class CertManagerTasks {
protected kubeHelper: KubeHelper
protected cheHelper: CheHelper

constructor(flags: any) {
this.kubeHelper = new KubeHelper(flags)
this.cheHelper = new CheHelper(flags)
}

/**
Expand Down Expand Up @@ -161,19 +163,15 @@ export class CertManagerTasks {
}
},
{
title: 'Add local Eclipse Che CA certificate into browser',
title: 'Retrieving Che self-signed CA certificate',
task: async (ctx: any, task: any) => {
const cheSecret = await this.kubeHelper.getSecret(CHE_TLS_SECRET_NAME, flags.chenamespace)
if (cheSecret && cheSecret.data) {
const cheCaCrt = Buffer.from(cheSecret.data['ca.crt'], 'base64').toString('ascii')
const cheCaPublicCertPath = path.join(os.homedir(), 'cheCA.crt')
fs.writeFileSync(cheCaPublicCertPath, cheCaCrt)

const yellow = '\x1b[33m'
const noColor = '\x1b[0m'
const message = `❗${yellow}[MANUAL ACTION REQUIRED]${noColor} Please add local Eclipse Che CA certificate into your browser: ${cheCaPublicCertPath}`
task.title = message
ctx.highlightedMessages.push(message)
const cheCaCertPath = await this.cheHelper.saveCheCaCert(cheCaCrt)

ctx.highlightedMessages.push(getMessageImportCaCertIntoBrowser(cheCaCertPath))
task.title = `${task.title}... is exported to ${cheCaCertPath}`
} else {
throw new Error('Failed to get Cert Manager CA secret')
}
Expand Down
Loading

0 comments on commit 7e99d04

Please sign in to comment.