UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

196 lines 9.43 kB
// SPDX-License-Identifier: Apache-2.0 var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { MissingArgumentError } from './errors/missing-argument-error.js'; import { SoloError } from './errors/solo-error.js'; import { Flags as flags } from '../commands/flags.js'; import fs from 'node:fs'; import { Templates } from './templates.js'; import { GrpcProxyTlsEnums } from './enumerations.js'; import { inject, injectable } from 'tsyringe-neo'; import { patchInject } from './dependency-injection/container-helper.js'; import { SecretType } from '../integration/kube/resources/secret/secret-type.js'; import { InjectTokens } from './dependency-injection/inject-tokens.js'; /** * Used to handle interactions with certificates data and inject it into the K8s cluster secrets */ let CertificateManager = class CertificateManager { k8Factory; logger; configManager; constructor(k8Factory, logger, configManager) { this.k8Factory = k8Factory; this.logger = logger; this.configManager = configManager; this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name); this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name); this.configManager = patchInject(configManager, InjectTokens.ConfigManager, this.constructor.name); } /** * Reads the certificate and key and build the secret with the appropriate structure * * @param cert - file path to the certificate file * @param key - file path to the key file * @param type - the certificate type if it's for gRPC or gRPC Web * * @returns the secret */ buildSecret(cert, key, type) { switch (type) { //? HAProxy case GrpcProxyTlsEnums.GRPC: { const certData = fs.readFileSync(cert).toString(); const keyData = fs.readFileSync(key).toString(); const pem = `${certData}\n${keyData}`; return { 'tls.pem': Buffer.from(pem).toString('base64'), }; } //? Envoy case GrpcProxyTlsEnums.GRPC_WEB: { return { 'tls.crt': fs.readFileSync(cert).toString('base64'), 'tls.key': fs.readFileSync(key).toString('base64'), }; } } } /** * Copies the TLS Certificates into K8s namespaced secret. * * @param nodeAlias - the alias of the node to which the TLS certificate should apply * @param cert - file path to the certificate file * @param key - file path to the key file * @param type - the certificate type if it's for gRPC or gRPC Web */ async copyTlsCertificate(nodeAlias, cert, key, type) { try { const data = this.buildSecret(cert, key, type); const name = Templates.renderGrpcTlsCertificatesSecretName(nodeAlias, type); const namespace = this.getNamespace(); const labels = Templates.renderGrpcTlsCertificatesSecretLabelObject(nodeAlias, type); const isSecretCreated = await this.k8Factory .default() .secrets() .createOrReplace(namespace, name, SecretType.OPAQUE, data, labels); if (!isSecretCreated) { throw new SoloError(`failed to create secret for TLS certificates for node '${nodeAlias}'`); } } catch (error) { const errorMessage = 'failed to copy tls certificate to secret ' + `'${Templates.renderGrpcTlsCertificatesSecretName(nodeAlias, type)}': ${error.message}`; throw new SoloError(errorMessage, error); } } /** * Creates sub-tasks for copying the TLS Certificates into K8s secrets for gRPC and gRPC Web * * @param task - Listr Task to which to attach the sub-tasks * @param grpcTlsCertificatePathsUnparsed - the unparsed (alias=path)[] for the gRPC Certificate * @param grpcWebTlsCertificatePathsUnparsed - the unparsed (alias=path)[] for the gRPC Web Certificate * @param grpcTlsKeyPathsUnparsed - the unparsed (alias=path)[] for the gRPC Certificate Key * @param grpcWebTlsKeyPathsUnparsed - the unparsed (alias=path)[] for the gRPC Web Certificate Key * * @returns the build sub-tasks for creating the secrets */ buildCopyTlsCertificatesTasks(task, grpcTlsCertificatePathsUnparsed, grpcWebTlsCertificatePathsUnparsed, grpcTlsKeyPathsUnparsed, grpcWebTlsKeyPathsUnparsed) { const subTasks = []; const grpcTlsParsedValues = { title: 'Copy gRPC TLS Certificate data', certType: GrpcProxyTlsEnums.GRPC, certs: this.parseAndValidate(grpcTlsCertificatePathsUnparsed, 'gRPC TLS Certificate paths'), keys: this.parseAndValidate(grpcTlsKeyPathsUnparsed, 'gRPC TLS Certificate Key paths'), }; const grpcWebTlsParsedValue = { certType: GrpcProxyTlsEnums.GRPC_WEB, title: 'Copy gRPC Web TLS data', certs: this.parseAndValidate(grpcWebTlsCertificatePathsUnparsed, 'gRPC Web TLS Certificate paths'), keys: this.parseAndValidate(grpcWebTlsKeyPathsUnparsed, 'gRPC Web Certificate TLS Key paths'), }; if (grpcTlsParsedValues.certs.length !== grpcTlsParsedValues.keys.length) { throw new SoloError("The structure of the gRPC TLS Certificate doesn't match" + `Certificates: ${grpcTlsCertificatePathsUnparsed}, Keys: ${grpcTlsKeyPathsUnparsed}`); } if (grpcTlsParsedValues.certs.length !== grpcTlsParsedValues.keys.length) { throw new SoloError("The structure of the gRPC Web TLS Certificate doesn't match" + `Certificates: ${grpcWebTlsCertificatePathsUnparsed}, Keys: ${grpcWebTlsKeyPathsUnparsed}`); } for (const { certType, title, certs, keys } of [grpcTlsParsedValues, grpcWebTlsParsedValue]) { if (certs.length === 0) { continue; } for (const [index, cert_] of certs.entries()) { const nodeAlias = cert_.nodeAlias; const cert = cert_.filePath; const key = keys[index].filePath; subTasks.push({ title: `${title} for node ${nodeAlias}`, task: () => this.copyTlsCertificate(nodeAlias, cert, key, certType), }); } } return task.newListr(subTasks, { concurrent: true, rendererOptions: { collapseSubtasks: false }, }); } /** * Handles parsing the unparsed data validating it follows the structure * * @param input - the unparsed data ( ex. node0=/usr/bob/grpc-web.cert ) * @param type - of the data being parsed for the error logging * * @returns an array of parsed data with node alias and the path * * @throws SoloError - if the data doesn't follow the structure */ parseAndValidate(input, type) { return input.split(',').map((line, index) => { if (!line.includes('=')) { throw new SoloError(`Failed to parse input ${input} of type ${type} on ${line}, index ${index}`); } const [nodeAlias, filePath] = line.split('='); if (!nodeAlias?.length || !filePath?.length) { throw new SoloError(`Failed to parse input ${input} of type ${type} on ${line}, index ${index}`); } let fileExists = false; try { fileExists = fs.statSync(filePath).isFile(); } catch { fileExists = false; } if (!fileExists) { throw new SoloError(`File doesn't exist on path ${input} input of type ${type} on ${line}, index ${index}`); } return { nodeAlias, filePath }; }); } getNamespace() { const ns = this.configManager.getFlag(flags.namespace); if (!ns) { throw new MissingArgumentError('namespace is not set'); } return ns; } }; CertificateManager = __decorate([ injectable(), __param(0, inject(InjectTokens.K8Factory)), __param(1, inject(InjectTokens.SoloLogger)), __param(2, inject(InjectTokens.ConfigManager)), __metadata("design:paramtypes", [Object, Object, Function]) ], CertificateManager); export { CertificateManager }; //# sourceMappingURL=certificate-manager.js.map