diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml new file mode 100644 index 0000000..0b559f2 --- /dev/null +++ b/.github/workflows/publish-package.yml @@ -0,0 +1,23 @@ +name: Publish package to NPM Registry +on: + release: + types: [published] +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v3 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + + - run: npm ci + + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 7b3f783..4d1198f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# env-sops -Library for encrypt/decrypt/load environment variables using SOPS+AGE +# sops-toolkit +Package to help encrypt/decrypt ENV files diff --git a/bin/decrypt.js b/bin/decrypt.js new file mode 100755 index 0000000..9a52823 --- /dev/null +++ b/bin/decrypt.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node +const SOPS_AGE_KEY_FILE = '~/.sops/baiken-age-key.txt' + +const { execSync } = require("child_process") +const { existsSync, readFileSync } = require('fs') +const { resolve, join } = require('path') +const [, , inputFile, outputFolder] = process.argv + +if (!inputFile) { throw new Error('Missing encrypted file.') } + +const sourceFile = resolve(process.cwd(), inputFile) +if (!existsSync(sourceFile)) { throw new Error(`Encrypted file(${sourceFile}) not exists.`) } + +if (!readFileSync(sourceFile, 'utf-8').toString().includes('sops_version')) { throw new Error(`File(${sourceFile}) is not encrypted.`) } + +if (!existsSync(outputFolder)) { throw new Error(`Second parameter the output folder(${outputFolder}) was not provided`) } + +const destinationFile = join(outputFolder, '.env') + +console.log(` +SOPS: decrypt +============= +SOPS_AGE_KEY_FILE: ${SOPS_AGE_KEY_FILE} +sourceFile: ${sourceFile} +destinationFile: ${destinationFile} +`) + +execSync(`sops --decrypt --age $(cat ${SOPS_AGE_KEY_FILE} | grep -oEi "public key: (.*)" | grep -oEi "\\b(\\w+)$") ${sourceFile} > ${destinationFile}`) + +console.log('encrypt', process.argv, sourceFile) \ No newline at end of file diff --git a/bin/encrypt.js b/bin/encrypt.js new file mode 100755 index 0000000..f115eb0 --- /dev/null +++ b/bin/encrypt.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node +const SOPS_AGE_KEY_FILE = '~/.sops/baiken-age-key.txt' + +const { execSync } = require("child_process") +const { existsSync, readFileSync } = require('fs') +const { resolve } = require('path') +const [, , inputFile] = process.argv + +if (!inputFile) { throw new Error('Missing encrypted file.') } + +const destinationPath = resolve(process.cwd(), inputFile) +if (!existsSync(destinationPath)) { throw new Error(`Encrypted file(${destinationPath}) not exists.`) } + +if (readFileSync(destinationPath,'utf-8').toString().includes('sops_version')) { throw new Error(`Encrypted file(${destinationPath}) already encrypted.`) } + +console.log(` +SOPS: encrypt +============= +SOPS_AGE_KEY_FILE: ${SOPS_AGE_KEY_FILE} +destinationPath: ${destinationPath} +`) + +execSync(`sops --encrypt --age "age1f6u2zw6323cx3lwtqeeusessqra5wg0curq6lxtqcql9yh0s5sgq2k38m9" -i ${destinationPath}`) + +console.log('encrypt', process.argv, destinationPath) \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..339637c --- /dev/null +++ b/index.js @@ -0,0 +1,5 @@ +const { loadEnvs } = require('./lib/load-envs') + +module.exports = { + loadEnvs +} \ No newline at end of file diff --git a/lib/load-envs.js b/lib/load-envs.js new file mode 100644 index 0000000..88f58a3 --- /dev/null +++ b/lib/load-envs.js @@ -0,0 +1,39 @@ +const SOPS_AGE_KEY_FILE = '~/.sops/baiken-age-key.txt' +const { config: dotEnv } = require('dotenv') +const { execSync } = require("child_process") +const { existsSync } = require('fs') +const { resolve, join } = require('path') + +module.exports.loadEnvs = async function (inputFile, rootFolder = process.cwd()) { + const AGE_PRIVATE_KEY = process?.env?.AGE_PRIVATE_KEY + console.log(` +SOPS: app decrypt and loading envs +============= +AGE_PRIVATE_KEY: ${AGE_PRIVATE_KEY} +SOPS_AGE_KEY_FILE: ${SOPS_AGE_KEY_FILE} +inputFile: ${inputFile} +destinationFile: ${rootFolder} +`) + const sourceFile = resolve(rootFolder, inputFile) + if (!existsSync(sourceFile)) { throw new Error(`Encrypted file(${sourceFile}) not exists.`) } + + const localEnvFile = join(rootFolder, 'local.env') + const destinationFile = join(rootFolder, '.env') + + if (process?.env?.AGE_PRIVATE_KEY) { + console.log(`Decrypting ${sourceFile} to ${destinationFile} using AGE_PRIVATE_KEY`) + execSync(`sops --decrypt ${sourceFile} > ${destinationFile}`) + } else { + console.log(`Decrypting ${sourceFile} to ${destinationFile} using SOPS_AGE_KEY_FILE`) + execSync(`sops --decrypt --age $(cat ${SOPS_AGE_KEY_FILE} | grep -oEi "public key: (.*)" | grep -oEi "\\b(\\w+)$") ${sourceFile} > ${destinationFile}`) + } + + if (existsSync(localEnvFile)) { + console.log(`Loading ${localEnvFile}`) + dotEnv({ path: localEnvFile }) + } + + dotEnv({ path: destinationFile }) +} + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c10bc5b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,38 @@ +{ + "name": "env-sops", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "env-sops", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "dotenv": "^16.3.1" + }, + "bin": { + "env-decrypt": "bin/decrypt.js", + "env-encrypt": "bin/encrypt.js" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + } + }, + "dependencies": { + "dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..44ea140 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "env-sops", + "version": "1.0.0", + "description": "Package to help encrypt/decrypt ENV files", + "main": "index.js", + "files": [ + "index.js", + "lib/**/*" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/mrosalesdiaz/env-sops.git" + }, + "author": "mrosalesdiaz@gmail.com", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/mrosalesdiaz/env-sops/issues" + }, + "homepage": "https://github.com/mrosalesdiaz/env-sops#readme", + "bin": { + "env-encrypt": "./bin/encrypt.js", + "env-decrypt": "./bin/decrypt.js" + }, + "dependencies": { + "dotenv": "^16.3.1" + } +} \ No newline at end of file