diff --git a/index.js b/index.js index b5c2672..d7dd41b 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ -import SyncSecret from "./src/SyncSecrets.js"; -import Decrypt from "./src/Decrypt.js"; -import logger from "./src/Logger.js"; -import path from "path"; +import SyncSecret from './src/SyncSecrets.js'; +import Decrypt from './src/Decrypt.js'; +import logger from './src/Logger.js'; +import path from 'path'; export default class SyncSecretPlugin { constructor(serverless, options) { @@ -10,7 +10,7 @@ export default class SyncSecretPlugin { this.servicePath = this.serverless.config.servicePath || process.cwd(); this.secrets = null; this.provider = this.serverless.getProvider('aws'); - this.stage = this.provider.getStage(); + this.stage = this.provider.getStage(); logger.setServerless(serverless); @@ -48,7 +48,7 @@ export default class SyncSecretPlugin { this.config.ssm_prefix ); try { - this.secrets = await decrypt.run(); + this.secrets = await decrypt.run(); } catch (e) { logger.logError(`Error decrypting secrets: ${e.message}`); throw e; @@ -111,7 +111,7 @@ export default class SyncSecretPlugin { config = { ...config, ...service.custom.syncSecrets }; const boolKeys = ['create_secret', 'delete_secret', 'show_values', 'dry']; - boolKeys.forEach(key => { + boolKeys.forEach((key) => { if (key in service.custom.syncSecrets) { logger.logInfo(`${key}: ${service.custom.syncSecrets[key]}`); config[key] = Boolean(service.custom.syncSecrets[key]); diff --git a/src/ChangeSet.js b/src/ChangeSet.js index 0f675a8..e30f934 100644 --- a/src/ChangeSet.js +++ b/src/ChangeSet.js @@ -1,13 +1,12 @@ -import lodash from "lodash"; -import chalk from "chalk"; -import SecretsManager from "./SecretsManager.js"; +import lodash from 'lodash'; +import chalk from 'chalk'; -const logPrefix = chalk.cyan("SecretKey"); -const skipTag = chalk.yellow("[SKIP]"); -const addedTag = chalk.green("[ADDED]"); -const changedTag = chalk.magenta("[CHANGED]"); -const removedTag = chalk.red("[REMOVED]"); -const valPlaceholder = "**********"; +const logPrefix = chalk.cyan('SecretKey'); +const skipTag = chalk.yellow('[SKIP]'); +const addedTag = chalk.green('[ADDED]'); +const changedTag = chalk.magenta('[CHANGED]'); +const removedTag = chalk.red('[REMOVED]'); +const valPlaceholder = '**********'; /** * ChangeSet is a class that represents a set of changes to be applied to secrets in AWS Secrets Manager. @@ -60,18 +59,11 @@ export default class ChangeSet { * @param {boolean} showValues A flag to un-hide secret values on log messages * @param {boolean} deleteSecret A flag to delete the secret */ - constructor( - smClient, - newValues, - existingValues, - skipPattern, - showValues = false, - deleteSecret = false, - ) { + constructor(smClient, newValues, existingValues, skipPattern, showValues = false, deleteSecret = false) { this.#changeDesc = []; this.#updatedValues = { ...existingValues }; this.#smClient = smClient; - this.#skipPattern = skipPattern || ""; + this.#skipPattern = skipPattern || ''; this.#showValues = showValues; this.#deleteSecret = deleteSecret; @@ -109,7 +101,7 @@ export default class ChangeSet { */ #eval(newValues, existingValues) { if (this.#deleteSecret) { - this.#removedDesc("ALL_KEYS"); + this.#removedDesc('ALL_KEYS'); return; } @@ -207,8 +199,6 @@ export default class ChangeSet { newVal = valPlaceholder; } - this.#changeDesc.push( - `${logPrefix}: ${changedTag} '${key}': '${oldVal}' => '${newVal}'`, - ); + this.#changeDesc.push(`${logPrefix}: ${changedTag} '${key}': '${oldVal}' => '${newVal}'`); } } diff --git a/src/Decrypt.js b/src/Decrypt.js index ac891fa..6821e1f 100644 --- a/src/Decrypt.js +++ b/src/Decrypt.js @@ -1,11 +1,11 @@ -import fs from "fs"; -import util from "util"; -import cp from "child_process"; -import os from "os"; -import path from "path"; -import crypto from "crypto"; -import lodash from "lodash"; -import logger from "./Logger.js"; +import fs from 'fs'; +import util from 'util'; +import cp from 'child_process'; +import os from 'os'; +import path from 'path'; +import crypto from 'crypto'; +import lodash from 'lodash'; +import logger from './Logger.js'; export default class Decrypt { #filePath; @@ -18,13 +18,8 @@ export default class Decrypt { * @param {string} filePath The path to the JSON file. * @param {string} privateKey Optional private key for encryption. * @param {string} ssm_prefix The SSM parameter name prefix for the private key. - */ - constructor( - serverless, - filePath, - privateKey, - ssm_prefix - ) { + */ + constructor(serverless, filePath, privateKey, ssm_prefix) { this.exec = util.promisify(cp.exec); this.provider = serverless.getProvider('aws'); this.#filePath = filePath; @@ -41,13 +36,13 @@ export default class Decrypt { async #setEjsonPrivateKey() { if (lodash.isNull(this.#ejsonPrivateKey) || lodash.isEmpty(this.#ejsonPrivateKey)) { if (lodash.isNull(this.#ssm_prefix) || lodash.isEmpty(this.#ssm_prefix)) { - throw new Error("No provided private key for decryption and no SSM prefix provided"); + throw new Error('No provided private key for decryption and no SSM prefix provided'); } this.#ejsonPrivateKey = await this.#getEjsonPrivateKey(); } return this; } - + /** * Validate the existence of the JSON file at the specified path and the private key. * @@ -65,27 +60,27 @@ export default class Decrypt { * @throws {Error} If any step of the process fails * @returns {Promise} The decrypted JSON object */ - async run() { - await this.#checkEjsonInstalled(); - await this.#setEjsonPrivateKey(); - return await this.#decrypt(); - } + async run() { + await this.#checkEjsonInstalled(); + await this.#setEjsonPrivateKey(); + return await this.#decrypt(); + } - /** + /** * Checks if ejson is installed in the system. - * + * * @throws {Error} If ejson is not installed * @returns {Promise} True if ejson is installed */ - async #checkEjsonInstalled() { - logger.logInfo('Checking if ejson is installed...'); - try { - await this.exec('which ejson'); - return true; - } catch (error) { - throw new Error('ejson command not found. Please install it first.'); - } + async #checkEjsonInstalled() { + logger.logInfo('Checking if ejson is installed...'); + try { + await this.exec('which ejson'); + return true; + } catch { + throw new Error('ejson command not found. Please install it first.'); } + } /** * Decrypt the JSON file using the ejson command and set the decrypted output. @@ -99,7 +94,7 @@ export default class Decrypt { let tmpKeyDir = null; let privateKeyPath = null; - try{ + try { const ejsonContent = JSON.parse(fs.readFileSync(this.#filePath, 'utf8')); const publicKey = ejsonContent?._public_key; const tmpdir = os.tmpdir(); @@ -109,8 +104,8 @@ export default class Decrypt { } tmpKeyDir = path.join(tmpdir, `${crypto.randomBytes(16).toString('hex')}`); - fs.mkdirSync(tmpKeyDir, { mode: 0o700 }); - + fs.mkdirSync(tmpKeyDir, { mode: 0o700 }); + privateKeyPath = path.join(tmpKeyDir, publicKey); fs.writeFileSync(privateKeyPath, this.#ejsonPrivateKey, { mode: 0o600 }); @@ -130,7 +125,7 @@ export default class Decrypt { logger.logInfo('Secrets decrypted successfully!'); return secrets; } catch (error) { - throw new Error(`Error decrypting secrets: ${error.message}`); + throw new Error(`Error decrypting secrets: ${error.message}`); } finally { if (!lodash.isNull(tmpKeyDir) && fs.existsSync(tmpKeyDir) && fs.existsSync(privateKeyPath)) { fs.unlinkSync(privateKeyPath); @@ -139,29 +134,28 @@ export default class Decrypt { } } - /** + /** * Get the private key from AWS SSM Parameter Store - * + * * @throws {Error} If the key cannot be retrieved * @returns {Promise} The private key */ async #getEjsonPrivateKey() { try { const result = await this.provider.request('SSM', 'getParameter', { - Name: this.#ssm_prefix, - WithDecryption: true - }); + Name: this.#ssm_prefix, + WithDecryption: true, + }); const privateKey = result.Parameter.Value; if (lodash.isEmpty(privateKey)) { - throw new Error("No provided private key for decryption"); + throw new Error('No provided private key for decryption'); } - return privateKey; + return privateKey; } catch (error) { - throw new Error(`Error getting private key from SSM: ${error.message}`); + throw new Error(`Error getting private key from SSM: ${error.message}`); } } - } diff --git a/src/Logger.js b/src/Logger.js index 264c1b5..2005540 100644 --- a/src/Logger.js +++ b/src/Logger.js @@ -1,7 +1,7 @@ -import chalk from "chalk"; +import chalk from 'chalk'; class Logger { - constructor(prefix = "SyncSecret") { + constructor(prefix = 'SyncSecret') { this.prefix = prefix; this.serverless = null; } @@ -9,7 +9,7 @@ class Logger { setServerless(serverless) { this.serverless = serverless; } - + /** * Print a plugin info log message * @@ -19,7 +19,7 @@ class Logger { this.serverless.cli.consoleLog(`${chalk.cyan(this.prefix)}: ${message}`); } - /** + /** * Print a plugin error log message * * @param {string} message Log message diff --git a/src/SecretsManager.js b/src/SecretsManager.js index 8d7444f..11a4b99 100644 --- a/src/SecretsManager.js +++ b/src/SecretsManager.js @@ -3,13 +3,12 @@ * secrets manager instance. */ export default class SecretsManager { - - /** + /** * Creates a new SecretsManager instance. - * + * * @param {Serverless} serverless The Serverless instance. * @param {string} secretName The name of the secret in AWS Secrets Manager. - * + * */ constructor(serverless, secretName) { this.secretName = secretName; @@ -21,7 +20,7 @@ export default class SecretsManager { * * @returns {Object} */ - async getValues(){ + async getValues() { const data = await this.provider.request('SecretsManager', 'getSecretValue', { SecretId: this.secretName, }); @@ -35,7 +34,7 @@ export default class SecretsManager { */ async update(newValues) { await this.provider.request('SecretsManager', 'updateSecret', { - SecretId: this.secretName, + SecretId: this.secretName, SecretString: JSON.stringify(newValues), }); } @@ -49,7 +48,7 @@ export default class SecretsManager { const result = await this.provider.request('SecretsManager', 'listSecrets', { Filters: [ { - Key: "name", + Key: 'name', Values: [this.secretName], }, ], diff --git a/src/SyncSecrets.js b/src/SyncSecrets.js index b3b6ce9..b3fa507 100644 --- a/src/SyncSecrets.js +++ b/src/SyncSecrets.js @@ -1,6 +1,6 @@ -import SecretsManager from "./SecretsManager.js"; -import ChangeSet from "./ChangeSet.js"; -import logger from "./Logger.js"; +import SecretsManager from './SecretsManager.js'; +import ChangeSet from './ChangeSet.js'; +import logger from './Logger.js'; /** * SyncSecret is a class representing an action to synchronize secrets with AWS Secrets Manager. @@ -44,7 +44,7 @@ export default class SyncSecret { /** * Creates a new SyncSecret instance. - * + * * @param {Serverless} serverless The Serverless instance. * @param {string} secretName The name of the secret in AWS Secrets Manager. * @param {Object} secrets The object containing the secret values. @@ -57,15 +57,7 @@ export default class SyncSecret { * * @throws {Error} Throws an error if any required parameter is missing or if the JSON file doesn't exist. */ - constructor( - serverless, - secretName, - secrets, - skipPattern, - showValues, - createSecret, - deleteSecret, - ) { + constructor(serverless, secretName, secrets, skipPattern, showValues, createSecret, deleteSecret) { this.#validateData(secretName); this.#secrets = secrets; this.#skipPattern = skipPattern; @@ -96,7 +88,7 @@ export default class SyncSecret { existingSecretData, this.#skipPattern, this.#showValues, - this.#deleteSecretFlag, + this.#deleteSecretFlag ); } @@ -105,7 +97,7 @@ export default class SyncSecret { */ async #createSecret() { if (this.#deleteSecretFlag || !this.#createSecretFlag) { - logger.logInfo("Secret creation skip..."); + logger.logInfo('Secret creation skip...'); return; } @@ -123,7 +115,7 @@ export default class SyncSecret { */ #validateData(secretName) { if (!secretName) { - throw new Error("Missing secret_name"); + throw new Error('Missing secret_name'); } } }