Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Use TLS secrets autogeneration mechanism in chectl #679

Merged
merged 2 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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')) {
tolusha marked this conversation as resolved.
Show resolved Hide resolved
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