UNPKG

gcp-secret-env

Version:

A simple tool to convert a google secret manager to an environment variable (in .env file or other)

308 lines (276 loc) 9.64 kB
#!/usr/bin/env node require("dotenv").config(); const fs = require("fs"); const path = require("path"); const inquirer = require("inquirer"); const { SecretManagerServiceClient } = require("@google-cloud/secret-manager"); const { convertEnvToJson, convertJsonToEnv, ensureFileExtension, } = require("./convertor"); const isWindows = process.platform === "win32" || path.sep === "\\"; const defaultCredentialsPath = isWindows ? `${process.env.APPDATA}\\gcloud\\application_default_credentials.json` : `${process.env.HOME}/.config/gcloud/application_default_credentials.json`; const defaultPersonalPath = isWindows ? `${process.env.APPDATA}\\gcp_secret_manager\\` : `${process.env.HOME}/.gcp_secret_manager/`; const defaultPersonalCredentialsPath = `${defaultPersonalPath}my.credentials.json`; const args = process.argv.splice(process.execArgv.length + 2); const params = {}; for (const [index, arg] of args.entries()) { if (!arg.startsWith("-")) continue; // Skip non-flag arguments let key, value; if (arg.includes("=")) { [key, value] = arg.slice(arg.startsWith("--") ? 2 : 1).split("="); } else { key = arg.slice(arg.startsWith("--") ? 2 : 1); const nextArg = args[index + 1]; if ( nextArg !== undefined && (!nextArg.startsWith("-") || !isNaN(Number(nextArg))) ) { value = nextArg; } else { value = true; } } params[key] = value; } async function init() { const userInfo = await inquirer.prompt([ { name: "credentials", type: "input", message: "Enter path of your defaults gcloud credentials (on lunix and macOS usually in: $HOME/.config/gcloud/application_default_credentials.json): ", }, ]); const credentials = fs.readFileSync(userInfo.credentials, "utf-8"); fs.writeFileSync(defaultPersonalCredentialsPath, credentials); process.env.GOOGLE_APPLICATION_CREDENTIALS = defaultPersonalCredentialsPath; launch(); } async function setSpecificCredentials(name = "") { const questions = [ { name: "credentials", message: "Enter path of your gcloud credentials: ", type: "input", validate: (value) => { if (fs.existsSync(value)) { return true; } else { return "Please enter a valid path"; } }, }, ]; if (!name) { questions.push({ name: "projectName", message: "Enter name of your new specific credentials (e.g: myProjectProd): ", type: "input", validate: (value) => { if (value.length) { return true; } else { return "Please enter a valid name"; } }, }); } const userInfo = await inquirer.prompt(questions); const credentials = fs.readFileSync(userInfo.credentials, "utf-8"); if (!fs.existsSync(defaultPersonalPath)) fs.mkdirSync(defaultPersonalPath); fs.writeFileSync( `${defaultPersonalPath}${name || userInfo.projectName}.json`, credentials ); } async function useParticularCredentials() { if (fs.existsSync(`${defaultPersonalPath}${params.upc}.json`)) { process.env.GOOGLE_APPLICATION_CREDENTIALS = `${defaultPersonalPath}${params.upc}.json`; launch(); } else { console.log("specific credentials not found"); await setSpecificCredentials(params.upc); process.env.GOOGLE_APPLICATION_CREDENTIALS = `${defaultPersonalPath}${params.upc}.json`; launch(); } } let specificUse; if (params?.spc) { console.log("set specific credentials", params?.spc); specificUse = "setCredentials"; } if (params?.upc) { console.log("use particular credentials", params?.upc); specificUse = "useParticularCredentials"; } switch (specificUse) { case "setCredentials": setSpecificCredentials(typeof params?.spc === "string" ? params?.spc : "") .then(() => { console.log( "credentials saved \n now you can run the command with the -upc flag to use this credentials" ); }) .catch((error) => { console.error(error); }); break; case "useParticularCredentials": useParticularCredentials(); break; default: if ( process.env.GOOGLE_APPLICATION_CREDENTIALS || fs.existsSync(defaultCredentialsPath) || fs.existsSync(defaultPersonalCredentialsPath) ) { if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) { process.env.GOOGLE_APPLICATION_CREDENTIALS = fs.existsSync( defaultCredentialsPath ) ? defaultCredentialsPath : defaultPersonalCredentialsPath; } launch(); } else { console.log("you must initialize your credentials"); init(); } break; } function launch() { const client = new SecretManagerServiceClient(); const getSecret = async (projectId, name, vers = "latest", envFileName) => { try { const [version] = await client.accessSecretVersion({ name: `projects/${projectId}/secrets/${name}/versions/${vers}`, }); const filePatch = envFileName.split("/"); filePatch.pop(); const filePath = filePatch.join("/"); if (filePatch.length > 0 && !fs.existsSync(filePath)) { fs.mkdirSync(filePath, { recursive: true }); } const payload = params?.json ? JSON.stringify( convertEnvToJson(version.payload.data.toString()), null, 2 ) : convertJsonToEnv(version.payload.data.toString()); const fileName = params?.json ? ensureFileExtension(envFileName, ".json") : ensureFileExtension(envFileName, ".env"); fs.writeFileSync(fileName, payload); console.info("\x1b[32mall done\x1b[0m"); } catch (error) { console.error(`\x1b[31mError: ${error?.details}\x1b[0m`); } }; const disableSecretVersion = async (projectId, name, vers) => { try { if (!isNaN(vers)) { let numericVersion = parseInt(vers, 10); if (numericVersion < 1) { const [{ name: currentVersion }] = await client.accessSecretVersion({ name: `projects/${projectId}/secrets/${name}/versions/latest`, }); numericVersion += parseInt(currentVersion.split("/").pop(), 10); } const [version] = await client.disableSecretVersion({ name: `projects/${projectId}/secrets/${name}/versions/${numericVersion}`, }); console.info(`Disabled secret version ${version.name}`); } else { const [version] = await client.disableSecretVersion({ name: `projects/${projectId}/secrets/${name}/versions/${vers}`, }); console.info(`\x1b[32mDisabled secret version ${version.name}\x1b[0m`); } } catch (error) { console.error(`\x1b[31mError: ${error?.details}\x1b[0m`); } }; const destroySecretVersion = async (projectId, name, vers) => { try { if (!isNaN(vers)) { let numericVersion = parseInt(vers, 10); if (numericVersion < 1) { const [{ name: currentVersion }] = await client.accessSecretVersion({ name: `projects/${projectId}/secrets/${name}/versions/latest`, }); numericVersion += parseInt(currentVersion.split("/").pop(), 10); } const [version] = await client.destroySecretVersion({ name: `projects/${projectId}/secrets/${name}/versions/${numericVersion}`, }); console.info(`Destroyed secret version ${version.name}`); } else { const [version] = await client.destroySecretVersion({ name: `projects/${projectId}/secrets/${name}/versions/${vers}`, }); console.info(`\x1b[32mDestroyed secret version ${version.name}\x1b[0m`); } } catch (error) { console.error(`\x1b[31mError: ${error?.details}\x1b[0m`); } }; const addSecretVersion = async (projectId, name, envFileName) => { const payload = Buffer.from(fs.readFileSync(envFileName, "utf-8"), "utf-8"); console.info("update secret"); try { const newPayload = params?.json ? JSON.stringify(convertEnvToJson(payload.toString()), null, 2) : convertJsonToEnv(payload.toString()); // Add a new version const [version] = await client.addSecretVersion({ parent: `projects/${projectId}/secrets/${name}`, payload: { data: Buffer.from(newPayload, "utf-8"), }, }); console.info(`\x1b[32mAdded secret version ${version.name}\x1b[0m`); } catch (error) { if (error.code === 5) { console.error( `\x1b[31mSecret ${name} not found. You need to create the secret in secret manager\x1b[0m` ); } else { console.error( `\x1b[31mError adding secret version: ${error?.details}\x1b[0m` ); } } }; const run = async () => { if (params?.name && params?.id) { const projectId = params.id; const secretName = params.name; const v = params?.version; const disableVersion = params?.disable; const destroyVersion = params?.destroy; const envFileName = params?.file || ".env"; params?.save ? await addSecretVersion(projectId, secretName, envFileName) : await getSecret(projectId, secretName, v, envFileName); if (disableVersion) { await disableSecretVersion(projectId, secretName, disableVersion); } if (destroyVersion) { await destroySecretVersion(projectId, secretName, destroyVersion); } } else { console.info( "you must provide the project id and name preceded by the -id and -name parameter" ); } }; run(); }