From 8cfcbaa12c1c353cfb283b4d5f18e9d12ca48989 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 11 Nov 2024 13:17:25 -0800 Subject: [PATCH 01/36] fix: correct secret reference validation check to permit referencing the same secret multiple times and improve error message --- .../src/services/secret-v2-bridge/secret-v2-bridge-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index 75b7fcca9..6b9740ffd 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -150,9 +150,9 @@ export const secretV2BridgeServiceFactory = ({ } }); - if (referredSecrets.length !== references.length) + if (new Set(referredSecrets.map((sec) => sec.key)).size !== new Set(references.map((sec) => sec.secretKey)).size) throw new BadRequestError({ - message: `Referenced secret not found. Found only ${diff( + message: `Referenced secret(s) not found: ${diff( references.map((el) => el.secretKey), referredSecrets.map((el) => el.key) ).join(",")}` From 14810de0542577ff4350827d82ca45db4483a972 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 11 Nov 2024 13:46:39 -0800 Subject: [PATCH 02/36] fix: correct secret reference value replacement to support special characters --- .../src/services/secret-v2-bridge/secret-v2-bridge-fns.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts index f1042d20c..22fc04db3 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts @@ -444,6 +444,7 @@ export const expandSecretReferencesFactory = ({ // eslint-disable-next-line no-continue if (depth > MAX_SECRET_REFERENCE_DEPTH) continue; const refs = value?.match(INTERPOLATION_SYNTAX_REG); + console.log("refs", refs); if (refs) { for (const interpolationSyntax of refs) { @@ -518,7 +519,10 @@ export const expandSecretReferencesFactory = ({ } if (referencedSecretValue) { - expandedValue = expandedValue.replaceAll(interpolationSyntax, referencedSecretValue); + expandedValue = expandedValue.replaceAll( + interpolationSyntax, + () => referencedSecretValue // prevents special characters from triggering replacement patterns + ); } } } From 4a3143e68996a58727ecb82cd534d777808a87ec Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 11 Nov 2024 14:04:36 -0800 Subject: [PATCH 03/36] fix: correct unique secret check to account for env and path --- .../services/secret-v2-bridge/secret-v2-bridge-service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index 6b9740ffd..d49a183ae 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -150,7 +150,11 @@ export const secretV2BridgeServiceFactory = ({ } }); - if (new Set(referredSecrets.map((sec) => sec.key)).size !== new Set(references.map((sec) => sec.secretKey)).size) + if ( + referredSecrets.length !== + new Set(references.map(({ secretKey, secretPath, environment }) => `${secretKey}.${secretPath}.${environment}`)) + .size // only count unique references + ) throw new BadRequestError({ message: `Referenced secret(s) not found: ${diff( references.map((el) => el.secretKey), From 334a728259230ce4df58d769bbf46b6e3df7ed48 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 11 Nov 2024 14:06:12 -0800 Subject: [PATCH 04/36] chore: remove console log --- backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts index 22fc04db3..95d2cdbf4 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts @@ -444,7 +444,6 @@ export const expandSecretReferencesFactory = ({ // eslint-disable-next-line no-continue if (depth > MAX_SECRET_REFERENCE_DEPTH) continue; const refs = value?.match(INTERPOLATION_SYNTAX_REG); - console.log("refs", refs); if (refs) { for (const interpolationSyntax of refs) { From b16ab6f763eb4376d68452af02d2a8baf53ec3cd Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 11 Oct 2023 11:11:06 +0530 Subject: [PATCH 05/36] feat: add script --- npm/src/index.cjs | 88 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 npm/src/index.cjs diff --git a/npm/src/index.cjs b/npm/src/index.cjs new file mode 100644 index 000000000..8ee818102 --- /dev/null +++ b/npm/src/index.cjs @@ -0,0 +1,88 @@ +const { execSync } = require("child_process"); +const fs = require("fs"); +const stream = require("node:stream"); +const tar = require("tar"); +const path = require("path"); +const zlib = require("zlib"); +const packageJSON = require("../package.json"); + +const supportedPlatforms = ["linux", "darwin", "win32", "freebsd"]; +const outputDir = "bin"; + +const getPlatform = () => { + const platform = process.platform; + if (!supportedPlatforms.includes(platform)) { + console.error( + "Your platform doesn't seem to be of type darwin, linux or windows" + ); + process.exit(1); + } + return platform; +}; + +const getArchitecture = () => { + const architecture = execSync("uname -m").toString().trim(); + let arch = ""; + if (architecture === "x86_64" || architecture === "amd64") { + arch = "amd64"; + } else if (architecture === "arm64" || architecture === "aarch64") { + arch = "arm64"; + } else if (architecture.startsWith("armv5")) { + arch = "armv5"; + } else if (architecture.startsWith("armv6")) { + arch = "armv6"; + } else if (architecture.startsWith("armv7")) { + arch = "armv7"; + } else if (architecture === "i386" || architecture === "i686") { + arch = "i386"; + } else { + console.error( + "Your architecture doesn't seem to be supported. Your architecture is", + architecture + ); + process.exit(1); + } + return arch; +}; + +function main() { + const PLATFORM = getPlatform(); + const ARCH = getArchitecture(); + const NUMERIC_RELEASE_VERSION = packageJSON.version; + const LATEST_RELEASE_VERSION = `v${NUMERIC_RELEASE_VERSION}`; + const downloadLink = `https://github.com/Infisical/infisical/releases/download/infisical-cli/${LATEST_RELEASE_VERSION}/infisical_${NUMERIC_RELEASE_VERSION}_${PLATFORM}_${ARCH}.tar.gz`; + + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir); + fetch(downloadLink, { + headers: { + Accept: "application/octet-stream", + }, + }) + .then(async (response) => { + if (!response.ok) { + throw new Error( + `Failed to fetch: ${response.status} - ${response.statusText}` + ); + } + + return new Promise((resolve, reject) => { + const outStream = stream.Readable.fromWeb(response.body) + .pipe(zlib.createGunzip()) + .pipe( + tar.x({ + C: path.join(outputDir), + filter: (path) => path === "infisical", + }) + ); + + outStream.on("error", reject); + outStream.on("close", resolve); + }); + }) + .catch((error) => { + console.error("Error downloading or extracting the asset:", error); + }); + } +} +main(); From 5d7a267f1d879d66c26ed6498519cb471acd465b Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 11 Oct 2023 11:13:13 +0530 Subject: [PATCH 06/36] chore: add package.json --- npm/package-lock.json | 112 ++++++++++++++++++++++++++++++++++++++++++ npm/package.json | 13 +++++ 2 files changed, 125 insertions(+) create mode 100644 npm/package-lock.json create mode 100644 npm/package.json diff --git a/npm/package-lock.json b/npm/package-lock.json new file mode 100644 index 000000000..e1e50a15c --- /dev/null +++ b/npm/package-lock.json @@ -0,0 +1,112 @@ +{ + "name": "infisical-cli", + "version": "0.14.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "infisical-cli", + "version": "0.14.2", + "hasInstallScript": true, + "dependencies": { + "tar": "^6.2.0" + }, + "bin": { + "infisical": "bin/infisical" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 000000000..8d2c5bdf5 --- /dev/null +++ b/npm/package.json @@ -0,0 +1,13 @@ +{ + "name": "infisical-cli", + "version": "0.14.2", + "bin": { + "infisical": "./bin/infisical" + }, + "scripts": { + "postinstall": "node src/index.cjs" + }, + "dependencies": { + "tar": "^6.2.0" + } +} From a55fe2b78891cd92312364872f7c0e25c2e97f36 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 11 Oct 2023 11:13:28 +0530 Subject: [PATCH 07/36] chore: add git ignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e76fd0c11..f2a23324b 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,5 @@ frontend-build cli/infisical-merge cli/test/infisical-merge /backend/binary + +/npm/bin From 3b02eedca69ae217929daeefa512432f899e4bb7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 20:36:09 +0400 Subject: [PATCH 08/36] feat: npm CLI --- .../workflows/release_build_infisical_cli.yml | 68 ++++++++- npm/.eslintrc.json | 9 ++ npm/README.md | 45 ++++++ npm/package-lock.json | 8 +- npm/package.json | 16 ++- npm/src/index.cjs | 131 +++++++++--------- 6 files changed, 200 insertions(+), 77 deletions(-) create mode 100644 npm/.eslintrc.json create mode 100644 npm/README.md diff --git a/.github/workflows/release_build_infisical_cli.yml b/.github/workflows/release_build_infisical_cli.yml index 02c349237..d270a9603 100644 --- a/.github/workflows/release_build_infisical_cli.yml +++ b/.github/workflows/release_build_infisical_cli.yml @@ -1,12 +1,15 @@ name: Build and release CLI on: + pull_request: + types: [opened, synchronize] + workflow_dispatch: - push: - # run only against tags - tags: - - "infisical-cli/v*.*.*" + # push: + # run only against tags + # tags: + # - "infisical-cli/v*.*.*" permissions: contents: write @@ -26,6 +29,63 @@ jobs: CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }} CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }} + npm-release: + runs-on: ubuntu-20.04 + env: + working-directory: ./npm + CLI_VERSION: 1.1.1 + needs: + - cli-integration-tests + # - goreleaser + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # - name: Extract version + # run: | + # VERSION=$(echo ${{ github.ref_name }} | sed 's/infisical-cli\/v//') + # echo "Version extracted: $VERSION" + # echo "CLI_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Print version + run: echo ${{ env.CLI_VERSION }} + + - name: Setup Node + uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + with: + node-version: 20 + cache: "npm" + cache-dependency-path: ./npm/package-lock.json + - name: Install dependencies + working-directory: ${{ env.working-directory }} + run: npm install + + - name: Set NPM version + working-directory: ${{ env.working-directory }} + run: npm version ${{ env.CLI_VERSION }} --allow-same-version --no-git-tag-version + + - name: Setup NPM + working-directory: ${{ env.working-directory }} + run: | + echo 'registry="https://registry.npmjs.org/"' > ./.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc + + echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Pack NPM + working-directory: ${{ env.working-directory }} + run: npm pack + + - name: Publish NPM + run: npm publish --tarball=./infisical-sdk-${{github.ref_name}} --access public --registry=https://registry.npmjs.org/ --dry-run + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + goreleaser: runs-on: ubuntu-20.04 needs: [cli-integration-tests] diff --git a/npm/.eslintrc.json b/npm/.eslintrc.json new file mode 100644 index 000000000..de8743bbb --- /dev/null +++ b/npm/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "env": { + "es6": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": "latest" + } +} diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 000000000..a27a5bc86 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,45 @@ +

Infisical

+

+

The open-source secret management platform: Sync secrets/configs across your team/infrastructure and prevent secret leaks.

+

+ +

+ Slack | + Infisical Cloud | + Self-Hosting | + Docs | + Website | + Hiring (Remote/SF) +

+ + +

+ + Infisical is released under the MIT license. + + + PRs welcome! + + + git commit activity + + + Cloudsmith downloads + + + Slack community channel + + + Infisical Twitter + +

+ +## Introduction + +**[Infisical](https://infisical.com)** is the open source secret management platform that teams use to centralize their application configuration and secrets like API keys and database credentials as well as manage their internal PKI. + +We're on a mission to make security tooling more accessible to everyone, not just security teams, and that means redesigning the entire developer experience from ground up. + +## [CLI Documentation](https://infisical.com/docs/cli/usage) + + diff --git a/npm/package-lock.json b/npm/package-lock.json index e1e50a15c..872adcf77 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -1,12 +1,12 @@ { - "name": "infisical-cli", - "version": "0.14.2", + "name": "@infisical/cli", + "version": "0.14.3", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "infisical-cli", - "version": "0.14.2", + "name": "@infisical/cli", + "version": "0.14.3", "hasInstallScript": true, "dependencies": { "tar": "^6.2.0" diff --git a/npm/package.json b/npm/package.json index 8d2c5bdf5..31859c598 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,9 +1,21 @@ { - "name": "infisical-cli", - "version": "0.14.2", + "name": "@infisical/cli", + "private": false, + "version": "0.0.0", + "keywords": [ + "infisical", + "cli", + "command-line" + ], "bin": { "infisical": "./bin/infisical" }, + "repository": { + "type": "git", + "url": "https://github.com/Infisical/infisical.git" + }, + "author": "Infisical Inc, ", + "scripts": { "postinstall": "node src/index.cjs" }, diff --git a/npm/src/index.cjs b/npm/src/index.cjs index 8ee818102..1f52111a0 100644 --- a/npm/src/index.cjs +++ b/npm/src/index.cjs @@ -1,4 +1,4 @@ -const { execSync } = require("child_process"); +const childProcess = require("child_process"); const fs = require("fs"); const stream = require("node:stream"); const tar = require("tar"); @@ -10,79 +10,76 @@ const supportedPlatforms = ["linux", "darwin", "win32", "freebsd"]; const outputDir = "bin"; const getPlatform = () => { - const platform = process.platform; - if (!supportedPlatforms.includes(platform)) { - console.error( - "Your platform doesn't seem to be of type darwin, linux or windows" - ); - process.exit(1); - } - return platform; + const platform = process.platform; + if (!supportedPlatforms.includes(platform)) { + console.error("Your platform doesn't seem to be of type darwin, linux or windows"); + process.exit(1); + } + return platform; }; const getArchitecture = () => { - const architecture = execSync("uname -m").toString().trim(); - let arch = ""; - if (architecture === "x86_64" || architecture === "amd64") { - arch = "amd64"; - } else if (architecture === "arm64" || architecture === "aarch64") { - arch = "arm64"; - } else if (architecture.startsWith("armv5")) { - arch = "armv5"; - } else if (architecture.startsWith("armv6")) { - arch = "armv6"; - } else if (architecture.startsWith("armv7")) { - arch = "armv7"; - } else if (architecture === "i386" || architecture === "i686") { - arch = "i386"; - } else { - console.error( - "Your architecture doesn't seem to be supported. Your architecture is", - architecture - ); - process.exit(1); - } - return arch; + const architecture = childProcess.execSync("uname -m").toString().trim(); + let arch = ""; + if (architecture === "x86_64" || architecture === "amd64") { + arch = "amd64"; + } else if (architecture === "arm64" || architecture === "aarch64") { + arch = "arm64"; + } else if (architecture.startsWith("armv5")) { + arch = "armv5"; + } else if (architecture.startsWith("armv6")) { + arch = "armv6"; + } else if (architecture.startsWith("armv7")) { + arch = "armv7"; + } else if (architecture === "i386" || architecture === "i686") { + arch = "i386"; + } else { + console.error("Your architecture doesn't seem to be supported. Your architecture is", architecture); + process.exit(1); + } + return arch; }; -function main() { - const PLATFORM = getPlatform(); - const ARCH = getArchitecture(); - const NUMERIC_RELEASE_VERSION = packageJSON.version; - const LATEST_RELEASE_VERSION = `v${NUMERIC_RELEASE_VERSION}`; - const downloadLink = `https://github.com/Infisical/infisical/releases/download/infisical-cli/${LATEST_RELEASE_VERSION}/infisical_${NUMERIC_RELEASE_VERSION}_${PLATFORM}_${ARCH}.tar.gz`; +async function main() { + const PLATFORM = getPlatform(); + const ARCH = getArchitecture(); + const NUMERIC_RELEASE_VERSION = packageJSON.version; + const LATEST_RELEASE_VERSION = `v${NUMERIC_RELEASE_VERSION}`; + const downloadLink = `https://github.com/Infisical/infisical/releases/download/infisical-cli/${LATEST_RELEASE_VERSION}/infisical_${NUMERIC_RELEASE_VERSION}_${PLATFORM}_${ARCH}.tar.gz`; - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir); - fetch(downloadLink, { - headers: { - Accept: "application/octet-stream", - }, - }) - .then(async (response) => { - if (!response.ok) { - throw new Error( - `Failed to fetch: ${response.status} - ${response.statusText}` - ); - } + // Ensure the output directory exists + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir); + } - return new Promise((resolve, reject) => { - const outStream = stream.Readable.fromWeb(response.body) - .pipe(zlib.createGunzip()) - .pipe( - tar.x({ - C: path.join(outputDir), - filter: (path) => path === "infisical", - }) - ); + // Download the latest CLI binary + try { + const response = await fetch(downloadLink, { + headers: { + Accept: "application/octet-stream" + } + }); - outStream.on("error", reject); - outStream.on("close", resolve); - }); - }) - .catch((error) => { - console.error("Error downloading or extracting the asset:", error); - }); - } + if (!response.ok) { + throw new Error(`Failed to fetch: ${response.status} - ${response.statusText}`); + } + + return await new Promise((resolve, reject) => { + const outStream = stream.Readable.fromWeb(response.body) + .pipe(zlib.createGunzip()) + .pipe( + tar.x({ + C: path.join(outputDir), + filter: path => path === "infisical" + }) + ); + + outStream.on("error", reject); + outStream.on("close", resolve); + }); + } catch (error) { + console.error("Error downloading or extracting the asset:", error); + process.exit(1); + } } main(); From 66e96018c4480635ff86f5f694496e4f9a3dba29 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 20:37:28 +0400 Subject: [PATCH 09/36] Update release_build_infisical_cli.yml --- .github/workflows/release_build_infisical_cli.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_build_infisical_cli.yml b/.github/workflows/release_build_infisical_cli.yml index d270a9603..4ce18e1fd 100644 --- a/.github/workflows/release_build_infisical_cli.yml +++ b/.github/workflows/release_build_infisical_cli.yml @@ -34,9 +34,9 @@ jobs: env: working-directory: ./npm CLI_VERSION: 1.1.1 - needs: - - cli-integration-tests - # - goreleaser + # needs: + # - cli-integration-tests + # - goreleaser steps: - uses: actions/checkout@v3 with: From df468e486568db8e152371f59bda518ec3ff5439 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 20:39:16 +0400 Subject: [PATCH 10/36] Update release_build_infisical_cli.yml --- .github/workflows/release_build_infisical_cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_build_infisical_cli.yml b/.github/workflows/release_build_infisical_cli.yml index 4ce18e1fd..36a0a6e4d 100644 --- a/.github/workflows/release_build_infisical_cli.yml +++ b/.github/workflows/release_build_infisical_cli.yml @@ -59,7 +59,7 @@ jobs: cache-dependency-path: ./npm/package-lock.json - name: Install dependencies working-directory: ${{ env.working-directory }} - run: npm install + run: npm install --ignore-scripts - name: Set NPM version working-directory: ${{ env.working-directory }} From ea3d164eadccfcf1a2286c3c35b1b86184f6f69c Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 20:40:45 +0400 Subject: [PATCH 11/36] Update release_build_infisical_cli.yml --- .github/workflows/release_build_infisical_cli.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release_build_infisical_cli.yml b/.github/workflows/release_build_infisical_cli.yml index 36a0a6e4d..37157c418 100644 --- a/.github/workflows/release_build_infisical_cli.yml +++ b/.github/workflows/release_build_infisical_cli.yml @@ -81,6 +81,7 @@ jobs: run: npm pack - name: Publish NPM + working-directory: ${{ env.working-directory }} run: npm publish --tarball=./infisical-sdk-${{github.ref_name}} --access public --registry=https://registry.npmjs.org/ --dry-run env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} From 4cd8e0fa67f5c5b5ee2e363100ce1de2c682b75d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 20:47:10 +0400 Subject: [PATCH 12/36] fix: workflow fixes --- .../workflows/release_build_infisical_cli.yml | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release_build_infisical_cli.yml b/.github/workflows/release_build_infisical_cli.yml index 37157c418..6ea6717e8 100644 --- a/.github/workflows/release_build_infisical_cli.yml +++ b/.github/workflows/release_build_infisical_cli.yml @@ -1,20 +1,16 @@ name: Build and release CLI on: - pull_request: - types: [opened, synchronize] - workflow_dispatch: - # push: - # run only against tags - # tags: - # - "infisical-cli/v*.*.*" + push: + # run only against tags + tags: + - "infisical-cli/v*.*.*" permissions: contents: write - # packages: write - # issues: write + jobs: cli-integration-tests: name: Run tests before deployment @@ -33,20 +29,19 @@ jobs: runs-on: ubuntu-20.04 env: working-directory: ./npm - CLI_VERSION: 1.1.1 - # needs: - # - cli-integration-tests - # - goreleaser + needs: + - cli-integration-tests + - goreleaser steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - # - name: Extract version - # run: | - # VERSION=$(echo ${{ github.ref_name }} | sed 's/infisical-cli\/v//') - # echo "Version extracted: $VERSION" - # echo "CLI_VERSION=$VERSION" >> $GITHUB_ENV + - name: Extract version + run: | + VERSION=$(echo ${{ github.ref_name }} | sed 's/infisical-cli\/v//') + echo "Version extracted: $VERSION" + echo "CLI_VERSION=$VERSION" >> $GITHUB_ENV - name: Print version run: echo ${{ env.CLI_VERSION }} From 7aba9c1a50bc1ad828add67aba039b878f76efa1 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 20:54:55 +0400 Subject: [PATCH 13/36] Update index.cjs --- npm/src/index.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/src/index.cjs b/npm/src/index.cjs index 1f52111a0..e5bf1610f 100644 --- a/npm/src/index.cjs +++ b/npm/src/index.cjs @@ -78,7 +78,7 @@ async function main() { outStream.on("close", resolve); }); } catch (error) { - console.error("Error downloading or extracting the asset:", error); + console.error("Error downloading or extracting Infisical CLI:", error); process.exit(1); } } From e330ddd5ee4886165320e57634fa6c6da3984f4f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 20:56:18 +0400 Subject: [PATCH 14/36] fix: remove dry run --- .github/workflows/release_build_infisical_cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_build_infisical_cli.yml b/.github/workflows/release_build_infisical_cli.yml index 6ea6717e8..3a3b384f3 100644 --- a/.github/workflows/release_build_infisical_cli.yml +++ b/.github/workflows/release_build_infisical_cli.yml @@ -77,7 +77,7 @@ jobs: - name: Publish NPM working-directory: ${{ env.working-directory }} - run: npm publish --tarball=./infisical-sdk-${{github.ref_name}} --access public --registry=https://registry.npmjs.org/ --dry-run + run: npm publish --tarball=./infisical-sdk-${{github.ref_name}} --access public --registry=https://registry.npmjs.org/ env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 2fe2ddd9fc8993940a5aa98619ff5c26eb58b648 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 21:17:53 +0400 Subject: [PATCH 15/36] Update package.json --- npm/src/index.cjs | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/npm/src/index.cjs b/npm/src/index.cjs index e5bf1610f..28fe0979b 100644 --- a/npm/src/index.cjs +++ b/npm/src/index.cjs @@ -19,24 +19,37 @@ const getPlatform = () => { }; const getArchitecture = () => { - const architecture = childProcess.execSync("uname -m").toString().trim(); + const architecture = process.arch; let arch = ""; - if (architecture === "x86_64" || architecture === "amd64") { + + if (architecture === "x64" || architecture === "amd64") { arch = "amd64"; - } else if (architecture === "arm64" || architecture === "aarch64") { + } else if (architecture === "arm64") { arch = "arm64"; - } else if (architecture.startsWith("armv5")) { - arch = "armv5"; - } else if (architecture.startsWith("armv6")) { - arch = "armv6"; - } else if (architecture.startsWith("armv7")) { - arch = "armv7"; - } else if (architecture === "i386" || architecture === "i686") { + } else if (architecture === "arm") { + // If the platform is Linux, we should find the exact ARM version, otherwise we default to armv7 which is the most common + if (process.platform === "linux" || process.platform === "freebsd") { + const output = childProcess.execSync("uname -m").toString().trim(); + + const armVersions = ["armv5", "armv6", "armv7"]; + + const armVersion = armVersions.find(version => output.startsWith(version)); + + if (armVersion) { + arch = armVersion; + } else { + arch = "armv7"; + } + } else { + arch = "armv7"; + } + } else if (architecture === "ia32") { arch = "i386"; } else { console.error("Your architecture doesn't seem to be supported. Your architecture is", architecture); process.exit(1); } + return arch; }; @@ -64,7 +77,7 @@ async function main() { throw new Error(`Failed to fetch: ${response.status} - ${response.statusText}`); } - return await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { const outStream = stream.Readable.fromWeb(response.body) .pipe(zlib.createGunzip()) .pipe( @@ -77,6 +90,11 @@ async function main() { outStream.on("error", reject); outStream.on("close", resolve); }); + + // Give the binary execute permissions if we're not on Windows + if (PLATFORM !== "win32") { + fs.chmodSync(path.join(outputDir, "infisical"), "755"); + } } catch (error) { console.error("Error downloading or extracting Infisical CLI:", error); process.exit(1); From 8cfc21751914fe8988cea20f2da176b2d276e4bb Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 21:38:34 +0400 Subject: [PATCH 16/36] Update README.md --- npm/README.md | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/npm/README.md b/npm/README.md index a27a5bc86..b64b02ccd 100644 --- a/npm/README.md +++ b/npm/README.md @@ -34,12 +34,38 @@ -## Introduction +### Introduction **[Infisical](https://infisical.com)** is the open source secret management platform that teams use to centralize their application configuration and secrets like API keys and database credentials as well as manage their internal PKI. We're on a mission to make security tooling more accessible to everyone, not just security teams, and that means redesigning the entire developer experience from ground up. -## [CLI Documentation](https://infisical.com/docs/cli/usage) + +### Installation + +The Infisical CLI NPM package serves as a new installation method in addition to our [existing installation methods](https://infisical.com/docs/cli/overview). + +After installing the CLI with the command below, you'll be able to use the infisical CLI across your machine. + +```bash +$ npm install -g @infisical/cli +``` + +Full example: +```bash +# Install the Infisical CLI +$ npm install -g @infisical/cli + +# Authenticate with the Infisical CLI +$ infisical login + +# Initialize your Infisical CLI +$ infisical init + +# List your secrets with Infisical CLI +$ infisical secrets +``` +### Documentation +Our full CLI documentation can be found [here](https://infisical.com/docs/cli/usage). \ No newline at end of file From 93c0313b288f342f5722367e2c0b1fb6dde06a6c Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 12 Nov 2024 21:48:04 +0400 Subject: [PATCH 17/36] docs: added NPM install option --- docs/cli/overview.mdx | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/cli/overview.mdx b/docs/cli/overview.mdx index ab913ec1a..397d1f474 100644 --- a/docs/cli/overview.mdx +++ b/docs/cli/overview.mdx @@ -9,7 +9,7 @@ You can use it across various environments, whether it's local development, CI/C ## Installation - + Use [brew](https://brew.sh/) package manager ```bash @@ -21,9 +21,8 @@ You can use it across various environments, whether it's local development, CI/C ```bash brew update && brew upgrade infisical ``` - - - + + Use [Scoop](https://scoop.sh/) package manager ```bash @@ -40,7 +39,20 @@ You can use it across various environments, whether it's local development, CI/C scoop update infisical ``` - + + + Use [NPM](https://www.npmjs.com/) package manager + + ```bash + npm install -g @infisical/cli + ``` + + ### Updates + + ```bash + npm update -g @infisical/cli + ``` + Install prerequisite ```bash From 7138b392f2e8993d8d1df7d4d298aff9b47223d6 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 12 Nov 2024 10:21:07 -0800 Subject: [PATCH 18/36] Feature: add ability to paste .env, .yml or .json secrets for upload and also fix upload when keys conflict but are not on current page --- .../src/server/routes/v1/dashboard-router.ts | 87 ++++++++++ .../secret-v2-bridge/secret-v2-bridge-dal.ts | 4 + .../secret-v2-bridge-types.ts | 2 + backend/src/services/secret/secret-types.ts | 1 + .../src/components/utilities/parseDotEnv.ts | 2 +- .../src/components/utilities/parseJson.ts | 11 ++ frontend/src/hooks/api/dashboard/queries.tsx | 19 +++ frontend/src/hooks/api/dashboard/types.ts | 11 ++ .../views/SecretMainPage/SecretMainPage.tsx | 1 - .../SecretDropzone/CopySecretsFromBoard.tsx | 2 + .../SecretDropzone/PasteSecretEnvModal.tsx | 132 ++++++++++++++++ .../SecretDropzone/SecretDropzone.tsx | 149 +++++++++++------- 12 files changed, 364 insertions(+), 57 deletions(-) create mode 100644 frontend/src/components/utilities/parseJson.ts create mode 100644 frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index 8213cf666..d3975bbc2 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -840,4 +840,91 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { }; } }); + + server.route({ + method: "GET", + url: "/secrets-by-keys", + config: { + rateLimit: secretsLimit + }, + schema: { + security: [ + { + bearerAuth: [] + } + ], + querystring: z.object({ + projectId: z.string().trim(), + environment: z.string().trim(), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash), + keys: z.string().trim().transform(decodeURIComponent) + }), + response: { + 200: z.object({ + secrets: secretRawSchema + .extend({ + secretPath: z.string().optional(), + tags: SecretTagsSchema.pick({ + id: true, + slug: true, + color: true + }) + .extend({ name: z.string() }) + .array() + .optional() + }) + .array() + .optional() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { secretPath, projectId, environment } = req.query; + + const keys = req.query.keys?.split(",").filter((key) => Boolean(key.trim())) ?? []; + if (!keys.length) throw new BadRequestError({ message: "One or more keys required" }); + + const { secrets } = await server.services.secret.getSecretsRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + environment, + actorAuthMethod: req.permission.authMethod, + projectId, + path: secretPath, + keys + }); + + await server.services.auditLog.createAuditLog({ + projectId, + ...req.auditLogInfo, + event: { + type: EventType.GET_SECRETS, + metadata: { + environment, + secretPath, + numberOfSecrets: secrets.length + } + } + }); + + if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) { + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretPulled, + distinctId: getTelemetryDistinctId(req), + properties: { + numberOfSecrets: secrets.length, + workspaceId: projectId, + environment, + secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + } + + return { secrets }; + } + }); }; diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts index 9ca3e87d3..3bdd5783f 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts @@ -361,6 +361,10 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => { void bd.whereILike(`${TableName.SecretV2}.key`, `%${filters?.search}%`); } } + + if (filters?.keys) { + void bd.whereIn(`${TableName.SecretV2}.key`, filters.keys); + } }) .where((bd) => { void bd.whereNull(`${TableName.SecretV2}.userId`).orWhere({ userId: userId || null }); diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts index e621f8edb..7216989ff 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts @@ -33,6 +33,7 @@ export type TGetSecretsDTO = { offset?: number; limit?: number; search?: string; + keys?: string[]; } & TProjectPermission; export type TGetASecretDTO = { @@ -294,6 +295,7 @@ export type TFindSecretsByFolderIdsFilter = { search?: string; tagSlugs?: string[]; includeTagsInSearch?: boolean; + keys?: string[]; }; export type TGetSecretsRawByFolderMappingsDTO = { diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index 7c09c9349..ca5c5a74b 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -185,6 +185,7 @@ export type TGetSecretsRawDTO = { offset?: number; limit?: number; search?: string; + keys?: string[]; } & TProjectPermission; export type TGetASecretRawDTO = { diff --git a/frontend/src/components/utilities/parseDotEnv.ts b/frontend/src/components/utilities/parseDotEnv.ts index bec3a506b..ac9c616b5 100644 --- a/frontend/src/components/utilities/parseDotEnv.ts +++ b/frontend/src/components/utilities/parseDotEnv.ts @@ -6,7 +6,7 @@ const LINE = * @param {ArrayBuffer} src - source buffer * @returns {String} text - text of buffer */ -export function parseDotEnv(src: ArrayBuffer) { +export function parseDotEnv(src: ArrayBuffer | string) { const object: { [key: string]: { value: string; comments: string[] }; } = {}; diff --git a/frontend/src/components/utilities/parseJson.ts b/frontend/src/components/utilities/parseJson.ts new file mode 100644 index 000000000..e6eb36f80 --- /dev/null +++ b/frontend/src/components/utilities/parseJson.ts @@ -0,0 +1,11 @@ +export const parseJson = (src: ArrayBuffer | string) => { + const file = src.toString(); + const formatedData: Record = JSON.parse(file); + const env: Record = {}; + Object.keys(formatedData).forEach((key) => { + if (typeof formatedData[key] === "string") { + env[key] = { value: formatedData[key], comments: [] }; + } + }); + return env; +}; diff --git a/frontend/src/hooks/api/dashboard/queries.tsx b/frontend/src/hooks/api/dashboard/queries.tsx index 7654ec07f..adff8bb0e 100644 --- a/frontend/src/hooks/api/dashboard/queries.tsx +++ b/frontend/src/hooks/api/dashboard/queries.tsx @@ -5,6 +5,7 @@ import axios from "axios"; import { createNotification } from "@app/components/notifications"; import { apiRequest } from "@app/config/request"; import { + DashboardProjectSecretsByKeys, DashboardProjectSecretsDetails, DashboardProjectSecretsDetailsResponse, DashboardProjectSecretsOverview, @@ -12,6 +13,7 @@ import { DashboardSecretsOrderBy, TDashboardProjectSecretsQuickSearch, TDashboardProjectSecretsQuickSearchResponse, + TGetDashboardProjectSecretsByKeys, TGetDashboardProjectSecretsDetailsDTO, TGetDashboardProjectSecretsOverviewDTO, TGetDashboardProjectSecretsQuickSearchDTO @@ -101,6 +103,23 @@ export const fetchProjectSecretsDetails = async ({ return data; }; +export const fetchDashboardProjectSecretsByKeys = async ({ + keys, + ...params +}: TGetDashboardProjectSecretsByKeys) => { + const { data } = await apiRequest.get( + "/api/v1/dashboard/secrets-by-keys", + { + params: { + ...params, + keys: encodeURIComponent(keys.join(",")) + } + } + ); + + return data; +}; + export const useGetProjectSecretsOverview = ( { projectId, diff --git a/frontend/src/hooks/api/dashboard/types.ts b/frontend/src/hooks/api/dashboard/types.ts index da121a349..444614b82 100644 --- a/frontend/src/hooks/api/dashboard/types.ts +++ b/frontend/src/hooks/api/dashboard/types.ts @@ -29,6 +29,10 @@ export type DashboardProjectSecretsDetailsResponse = { totalCount: number; }; +export type DashboardProjectSecretsByKeys = { + secrets: SecretV3Raw[]; +}; + export type DashboardProjectSecretsOverview = Omit< DashboardProjectSecretsOverviewResponse, "secrets" @@ -89,3 +93,10 @@ export type TGetDashboardProjectSecretsQuickSearchDTO = { search: string; environments: string[]; }; + +export type TGetDashboardProjectSecretsByKeys = { + projectId: string; + secretPath: string; + environment: string; + keys: string[]; +}; diff --git a/frontend/src/views/SecretMainPage/SecretMainPage.tsx b/frontend/src/views/SecretMainPage/SecretMainPage.tsx index 31635884c..85d25a56f 100644 --- a/frontend/src/views/SecretMainPage/SecretMainPage.tsx +++ b/frontend/src/views/SecretMainPage/SecretMainPage.tsx @@ -552,7 +552,6 @@ const SecretMainPageContent = () => { {(isAllowed) => (