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
JavaScript
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();
}