gitops-secrets
Version:
SecretOps workflow for bundling encrypted secrets into your deployments to safely decrypt at runtime.
141 lines (120 loc) • 5.79 kB
JavaScript
const fs = require("fs");
const path = require("path");
const secrets = require("../src/index");
const secretsNofs = require("../src/no-fs");
// eslint-disable-next-line security/detect-non-literal-fs-filename
const read = (file) => fs.readFileSync(path.resolve(file), { encoding: "utf8" });
// eslint-disable-next-line security/detect-non-literal-fs-filename
const rm = (...files) => files.forEach((file) => fs.unlinkSync(path.resolve(file)));
const PROCESS_ENV = { ...process.env };
const NPM_PACKAGE_TYPE = process.env.npm_package_type;
// NOTE: these test values are not sensitive
const GITOPS_SECRETS_MASTER_KEY = "1e18cc54-1d77-45a1-ae46-fecebce35ae2";
const SECRETS = {
API_KEY: "46f181e0-d68c-49d2-aa4c-1dd30d954877",
AUTH_TOKEN: "cb71114f-22c3-4a66-af06-39d8d39a2af3",
};
// Previously encrypted CIPHER of the SECRETS object for testing decryption backwards compatibility
const SECRETS_CIPHER =
"base64:1000000:6Mb/k90J0ts=:ckoXxlCYKWPdpeQx:g+vDP451CfU8lJeqZfKl9rzGuZZppQk50espQMI+VR59zz/JHwMjdEIYMMzZD/zcm0vmri1AzMan4J4lQmcIJSsAKtkRtvqX0Je5RxBIrRJD5gDoz3SH4B7qm78Rb2h9FTiZU+MD1am+Pwc5cEw88X4l+46OOg==";
beforeEach(() => {
process.env = { ...PROCESS_ENV, GITOPS_SECRETS_MASTER_KEY: GITOPS_SECRETS_MASTER_KEY, npm_package_type: NPM_PACKAGE_TYPE };
});
test("Fail when process.env.GITOPS_SECRETS_MASTER_KEY is undefined", () => {
process.env.GITOPS_SECRETS_MASTER_KEY = undefined;
expect(() => secrets.encrypt(SECRETS)).toThrow();
expect(() => secrets.masterKey()).toThrow();
});
test("Fail when process.env.GITOPS_SECRETS_MASTER_KEY is less than 16 chars", () => {
process.env.GITOPS_SECRETS_MASTER_KEY = "6791f8e3";
expect(() => secrets.encrypt(SECRETS)).toThrow();
expect(() => secrets.masterKey()).toThrow();
});
test("Encrypt and decrypt in memory", () => {
expect(secrets.decrypt(secrets.encrypt(SECRETS))).toHaveProperty("API_KEY");
});
test("Ensure decryption backwards compatibility", () => {
expect(secrets.decrypt(SECRETS_CIPHER)).toHaveProperty("API_KEY");
});
test("Import secrets from ./no-fs sub-path has complete secrets API", () => {
expect(secretsNofs.decrypt(secretsNofs.encrypt(SECRETS))).toHaveProperty("API_KEY");
expect(secretsNofs.loadSecretsFromCipher(SECRETS_CIPHER)).toHaveProperty("API_KEY");
expect(process.env).not.toHaveProperty(`API_KEY`);
secretsNofs.populateEnv(secretsNofs.decrypt(SECRETS_CIPHER));
expect(process.env).toHaveProperty(`API_KEY`);
});
test("Import secrets from ./no-fs sub-path does not contain secret-files exports", () => {
Object.keys(require("../src/secrets-files")).forEach((exp) => expect(secretsNofs).not.toHaveProperty(exp));
});
test("Secrets build", async () => {
await secrets.build(SECRETS);
expect(secrets.loadSecrets()).toHaveProperty(`API_KEY`);
rm(secrets.DEFAULT_JS_PATH);
});
test("Secrets build with populateEnv", async () => {
expect(process.env).not.toHaveProperty(`API_KEY`);
await secrets.build(SECRETS);
const payload = secrets.loadSecrets().populateEnv();
expect(process.env).toHaveProperty(`API_KEY`);
expect(payload).not.toHaveProperty(`PATH`);
rm(secrets.DEFAULT_JS_PATH);
});
test("Secrets build with a path", async () => {
const SECRETS_PATH = ".secrets/custom.enc.js";
await secrets.build(SECRETS, { path: SECRETS_PATH });
// eslint-disable-next-line security/detect-non-literal-require
expect(require(`../${SECRETS_PATH}`).loadSecrets()).toHaveProperty("API_KEY");
rm(SECRETS_PATH);
});
test("Secrets build outputs in CommonJS format, even if project uses modules as require is performed locally", async () => {
process.env.npm_package_type = "module";
await secrets.build(SECRETS);
expect(read(secrets.DEFAULT_JS_PATH)).toContain("module.exports");
rm(secrets.DEFAULT_JS_PATH);
});
test("Secrets build outputs in ES modules format path is provided", async () => {
const SECRETS_PATH = ".secrets/custom.enc.js";
const NPM_PACKAGE_TYPE = process.env.npm_package_type;
process.env.npm_package_type = "module";
await secrets.build(SECRETS, { path: SECRETS_PATH });
expect(read(SECRETS_PATH)).toContain("export {");
rm(SECRETS_PATH);
process.env.npm_package_type = NPM_PACKAGE_TYPE;
});
test("Secrets build only exports CIPHER_TEXT if options.cipherTextOnly is true for CommonJS", async () => {
await secrets.build(SECRETS, { cipherTextOnly: true });
expect(read(secrets.DEFAULT_JS_PATH)).not.toContain("loadSecrets");
rm(secrets.DEFAULT_JS_PATH);
process.env.npm_package_type = NPM_PACKAGE_TYPE;
});
test("Secrets build only exports CIPHER_TEXT if options.cipherTextOnly is true for ES modules", async () => {
process.env.npm_package_type = "module";
await secrets.build(SECRETS, { cipherTextOnly: true });
expect(read(secrets.DEFAULT_JS_PATH)).not.toContain("loadSecrets");
rm(secrets.DEFAULT_JS_PATH);
process.env.npm_package_type = NPM_PACKAGE_TYPE;
});
test("Encrypt to JSON file", async () => {
secrets.encryptToFile(SECRETS);
expect(secrets.decryptFromFile()).toHaveProperty("API_KEY");
rm(secrets.DEFAULT_FILE_PATH);
});
test("Encrypt to JSON file with populateEnv", () => {
secrets.encryptToFile(SECRETS);
const payload = secrets.decryptFromFile();
expect(process.env).not.toHaveProperty("API_KEY");
expect(payload).toHaveProperty("API_KEY");
payload.populateEnv();
expect(process.env).toHaveProperty("API_KEY");
rm(secrets.DEFAULT_FILE_PATH);
});
test("Encrypt to JSON file with path", () => {
const SECRETS_PATH = ".secrets/custom.enc.json";
secrets.encryptToFile(SECRETS, { path: SECRETS_PATH });
const payload = secrets.decryptFromFile(SECRETS_PATH);
expect(process.env).not.toHaveProperty("API_KEY");
expect(payload).toHaveProperty("API_KEY");
payload.populateEnv();
expect(process.env).toHaveProperty("API_KEY");
rm(SECRETS_PATH);
});