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.
+
+
+
+
+
+
+
+## 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) => (
}
onClick={() => onToggle(true)}
isDisabled={!isAllowed}
variant="star"
diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
new file mode 100644
index 000000000..1e32caf47
--- /dev/null
+++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
@@ -0,0 +1,132 @@
+import { useForm } from "react-hook-form";
+import { subject } from "@casl/ability";
+import { faPaste } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+
+import { ProjectPermissionCan } from "@app/components/permissions";
+import { parseDotEnv } from "@app/components/utilities/parseDotEnv";
+import { parseJson } from "@app/components/utilities/parseJson";
+import {
+ Button,
+ FormControl,
+ Modal,
+ ModalContent,
+ ModalTrigger,
+ TextArea
+} from "@app/components/v2";
+import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
+
+type Props = {
+ isOpen?: boolean;
+ isSmaller?: boolean;
+ onToggle: (isOpen: boolean) => void;
+ onParsedEnv: (env: Record) => void;
+ environment: string;
+ secretPath: string;
+};
+
+const formSchema = z.object({
+ value: z.string().trim()
+});
+
+type TForm = z.infer;
+
+const PasteEnvForm = ({ onParsedEnv }: Pick) => {
+ const {
+ handleSubmit,
+ register,
+ formState: { isDirty, errors },
+ setError,
+ setFocus
+ } = useForm({ defaultValues: { value: "" }, resolver: zodResolver(formSchema) });
+
+ const onSubmit = ({ value }: TForm) => {
+ let env: Record;
+ try {
+ env = parseJson(value);
+ } catch (e) {
+ // not json, parse as env
+ env = parseDotEnv(value);
+ }
+
+ if (!Object.keys(env).length) {
+ setError("value", { message: "No secrets found." });
+ setFocus("value");
+ return;
+ }
+
+ onParsedEnv(env);
+ };
+
+ return (
+
+ );
+};
+
+export const PasteSecretEnvModal = ({
+ isSmaller,
+ isOpen,
+ onParsedEnv,
+ onToggle,
+ environment,
+ secretPath
+}: Props) => {
+ return (
+
+
+
+
+ {(isAllowed) => (
+ }
+ onClick={() => onToggle(true)}
+ isDisabled={!isAllowed}
+ variant="star"
+ size={isSmaller ? "xs" : "sm"}
+ >
+ Paste Secret Values
+
+ )}
+
+
+
+
+ {
+ onToggle(false);
+ onParsedEnv(value);
+ }}
+ />
+
+
+ );
+};
diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx
index 82d609fac..017fce7fa 100644
--- a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx
+++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx
@@ -1,7 +1,7 @@
import { ChangeEvent, DragEvent } from "react";
import { useTranslation } from "react-i18next";
import { subject } from "@casl/ability";
-import { faUpload } from "@fortawesome/free-solid-svg-icons";
+import { faPlus, faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQueryClient } from "@tanstack/react-query";
import { twMerge } from "tailwind-merge";
@@ -10,29 +10,22 @@ import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality
import { parseDotEnv } from "@app/components/utilities/parseDotEnv";
+import { parseJson } from "@app/components/utilities/parseJson";
import { Button, Modal, ModalContent } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks";
import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api";
-import { dashboardKeys } from "@app/hooks/api/dashboard/queries";
+import {
+ dashboardKeys,
+ fetchDashboardProjectSecretsByKeys
+} from "@app/hooks/api/dashboard/queries";
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
import { secretKeys } from "@app/hooks/api/secrets/queries";
-import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/types";
+import { SecretType } from "@app/hooks/api/types";
import { PopUpNames, usePopUpAction } from "../../SecretMainPage.store";
import { CopySecretsFromBoard } from "./CopySecretsFromBoard";
-
-const parseJson = (src: ArrayBuffer) => {
- 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;
-};
+import { PasteSecretEnvModal } from "./PasteSecretEnvModal";
type TParsedEnv = Record;
type TSecOverwriteOpt = { update: TParsedEnv; create: TParsedEnv };
@@ -43,7 +36,6 @@ type Props = {
workspaceId: string;
environment: string;
secretPath: string;
- secrets?: SecretV3RawSanitized[];
isProtectedBranch?: boolean;
};
@@ -53,7 +45,6 @@ export const SecretDropzone = ({
workspaceId,
environment,
secretPath,
- secrets = [],
isProtectedBranch = false
}: Props): JSX.Element => {
const { t } = useTranslation();
@@ -62,7 +53,8 @@ export const SecretDropzone = ({
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
"importSecEnv",
- "overlapKeyWarning"
+ "confirmUpload",
+ "pasteSecEnv"
] as const);
const queryClient = useQueryClient();
const { openPopUp } = usePopUpAction();
@@ -86,20 +78,10 @@ export const SecretDropzone = ({
}
};
- const handleParsedEnv = (env: TParsedEnv) => {
- const secretsGroupedByKey = secrets?.reduce>(
- (prev, curr) => ({ ...prev, [curr.key]: true }),
- {}
- );
- const overlappedSecrets = Object.keys(env)
- .filter((secKey) => secretsGroupedByKey?.[secKey])
- .reduce((prev, curr) => ({ ...prev, [curr]: env[curr] }), {});
+ const handleParsedEnv = async (env: TParsedEnv) => {
+ const envSecretKeys = Object.keys(env);
- const nonOverlappedSecrets = Object.keys(env)
- .filter((secKey) => !secretsGroupedByKey?.[secKey])
- .reduce((prev, curr) => ({ ...prev, [curr]: env[curr] }), {});
-
- if (!Object.keys(overlappedSecrets).length && !Object.keys(nonOverlappedSecrets).length) {
+ if (!envSecretKeys.length) {
createNotification({
type: "error",
text: "Failed to find secrets"
@@ -107,10 +89,42 @@ export const SecretDropzone = ({
return;
}
- handlePopUpOpen("overlapKeyWarning", {
- update: overlappedSecrets,
- create: nonOverlappedSecrets
- });
+ try {
+ setIsLoading.on();
+ const { secrets: existingSecrets } = await fetchDashboardProjectSecretsByKeys({
+ secretPath,
+ environment,
+ projectId: workspaceId,
+ keys: envSecretKeys
+ });
+
+ const secretsGroupedByKey = existingSecrets.reduce>(
+ (prev, curr) => ({ ...prev, [curr.secretKey]: true }),
+ {}
+ );
+
+ const updateSecrets = Object.keys(env)
+ .filter((secKey) => secretsGroupedByKey[secKey])
+ .reduce((prev, curr) => ({ ...prev, [curr]: env[curr] }), {});
+
+ const createSecrets = Object.keys(env)
+ .filter((secKey) => !secretsGroupedByKey[secKey])
+ .reduce((prev, curr) => ({ ...prev, [curr]: env[curr] }), {});
+
+ handlePopUpOpen("confirmUpload", {
+ update: updateSecrets,
+ create: createSecrets
+ });
+ } catch (e) {
+ console.error(e);
+ createNotification({
+ text: "Failed to check for secret conflicts",
+ type: "error"
+ });
+ handlePopUpClose("confirmUpload");
+ } finally {
+ setIsLoading.off();
+ }
};
const parseFile = (file?: File, isJson?: boolean) => {
@@ -160,7 +174,7 @@ export const SecretDropzone = ({
};
const handleSaveSecrets = async () => {
- const { update, create } = popUp?.overlapKeyWarning?.data as TSecOverwriteOpt;
+ const { update, create } = popUp?.confirmUpload?.data as TSecOverwriteOpt;
try {
if (Object.keys(create || {}).length) {
await createSecretBatch({
@@ -195,7 +209,7 @@ export const SecretDropzone = ({
dashboardKeys.getDashboardSecrets({ projectId: workspaceId, secretPath })
);
queryClient.invalidateQueries(secretApprovalRequestKeys.count({ workspaceId }));
- handlePopUpClose("overlapKeyWarning");
+ handlePopUpClose("confirmUpload");
createNotification({
type: "success",
text: isProtectedBranch
@@ -211,10 +225,16 @@ export const SecretDropzone = ({
}
};
- const isUploadedDuplicateSecretsEmpty = !Object.keys(
- (popUp.overlapKeyWarning?.data as TSecOverwriteOpt)?.update || {}
+ const createSecretCount = Object.keys(
+ (popUp.confirmUpload?.data as TSecOverwriteOpt)?.create || {}
).length;
+ const updateSecretCount = Object.keys(
+ (popUp.confirmUpload?.data as TSecOverwriteOpt)?.update || {}
+ ).length;
+
+ const isNonConflictingUpload = !updateSecretCount;
+
return (
-
+
+
handlePopUpToggle("pasteSecEnv", isOpen)}
+ onParsedEnv={handleParsedEnv}
+ environment={environment}
+ secretPath={secretPath}
+ isSmaller={isSmaller}
+ />
handlePopUpToggle("importSecEnv", isOpen)}
@@ -301,11 +329,12 @@ export const SecretDropzone = ({
>
{(isAllowed) => (
}
onClick={() => openPopUp(PopUpNames.CreateSecretForm)}
variant="star"
isDisabled={!isAllowed}
>
- Add a new secret
+ Add a New Secret
)}
@@ -315,25 +344,25 @@ export const SecretDropzone = ({
)}
handlePopUpToggle("overlapKeyWarning", open)}
+ isOpen={popUp?.confirmUpload?.isOpen}
+ onOpenChange={(open) => handlePopUpToggle("confirmUpload", open)}
>
- {isUploadedDuplicateSecretsEmpty ? "Upload" : "Overwrite"}
+ {isNonConflictingUpload ? "Upload" : "Overwrite"}
,
handlePopUpClose("overlapKeyWarning")}
+ className="ml-4"
+ onClick={() => handlePopUpClose("confirmUpload")}
variant="outline_bg"
isDisabled={isSubmitting}
>
@@ -341,17 +370,27 @@ export const SecretDropzone = ({
]}
>
- {isUploadedDuplicateSecretsEmpty ? (
- Upload secrets from this file
+ {isNonConflictingUpload ? (
+
+ Are you sure you want to import {createSecretCount} secret
+ {createSecretCount > 1 ? "s" : ""} to this environment?
+
) : (
-
-
Your file contains following duplicate secrets
-
- {Object.keys((popUp?.overlapKeyWarning?.data as TSecOverwriteOpt)?.update || {})
+
+
Your project already contains the following {updateSecretCount} secrets:
+
+ {Object.keys((popUp?.confirmUpload?.data as TSecOverwriteOpt)?.update || {})
?.map((key) => key)
.join(", ")}
-
Are you sure you want to overwrite these secrets and create other ones?
+
+ Are you sure you want to overwrite these secrets
+ {createSecretCount > 0
+ ? ` and import ${createSecretCount} new
+ one${createSecretCount > 1 ? "s" : ""}`
+ : ""}
+ ?
+
)}
From ddcf5b576b6d2ffdd0a71ac2d9eb07f0de15a9bd Mon Sep 17 00:00:00 2001
From: Scott Wilson
Date: Tue, 12 Nov 2024 10:25:23 -0800
Subject: [PATCH 19/36] improvement: improve field error message
---
.../components/SecretDropzone/PasteSecretEnvModal.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
index 1e32caf47..da4be8301 100644
--- a/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
+++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
@@ -52,7 +52,9 @@ const PasteEnvForm = ({ onParsedEnv }: Pick) => {
}
if (!Object.keys(env).length) {
- setError("value", { message: "No secrets found." });
+ setError("value", {
+ message: "No secrets found. Please make sure the provided format is valid."
+ });
setFocus("value");
return;
}
From 8b781b925a161f51c7ff372d96ecf15716778463 Mon Sep 17 00:00:00 2001
From: Daniel Hougaard
Date: Tue, 12 Nov 2024 22:45:37 +0400
Subject: [PATCH 20/36] fix: npm cli symlink
---
npm/package-lock.json | 4 ++--
npm/package.json | 3 +--
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/npm/package-lock.json b/npm/package-lock.json
index 872adcf77..30ea704bd 100644
--- a/npm/package-lock.json
+++ b/npm/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@infisical/cli",
- "version": "0.14.3",
+ "version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@infisical/cli",
- "version": "0.14.3",
+ "version": "0.0.0",
"hasInstallScript": true,
"dependencies": {
"tar": "^6.2.0"
diff --git a/npm/package.json b/npm/package.json
index 31859c598..c7b2ee7d3 100644
--- a/npm/package.json
+++ b/npm/package.json
@@ -8,14 +8,13 @@
"command-line"
],
"bin": {
- "infisical": "./bin/infisical"
+ "infisical": "bin/infisical"
},
"repository": {
"type": "git",
"url": "https://github.com/Infisical/infisical.git"
},
"author": "Infisical Inc, ",
-
"scripts": {
"postinstall": "node src/index.cjs"
},
From 1f2b0443cc3b74659eff46a696d4a86cbddc03f4 Mon Sep 17 00:00:00 2001
From: Scott Wilson
Date: Tue, 12 Nov 2024 16:11:47 -0800
Subject: [PATCH 21/36] improvement: address requested changes
---
frontend/src/components/dashboard/DropZone.tsx | 2 +-
frontend/src/components/utilities/parseJson.ts | 11 -----------
.../utilities/{parseDotEnv.ts => parseSecrets.ts} | 12 ++++++++++++
.../SecretDropzone/PasteSecretEnvModal.tsx | 5 ++---
.../components/SecretDropzone/SecretDropzone.tsx | 3 +--
5 files changed, 16 insertions(+), 17 deletions(-)
delete mode 100644 frontend/src/components/utilities/parseJson.ts
rename frontend/src/components/utilities/{parseDotEnv.ts => parseSecrets.ts} (81%)
diff --git a/frontend/src/components/dashboard/DropZone.tsx b/frontend/src/components/dashboard/DropZone.tsx
index b196e8cd4..ab246c377 100644
--- a/frontend/src/components/dashboard/DropZone.tsx
+++ b/frontend/src/components/dashboard/DropZone.tsx
@@ -11,7 +11,7 @@ import { SecretType } from "@app/hooks/api/types";
import Button from "../basic/buttons/Button";
import Error from "../basic/Error";
import { createNotification } from "../notifications";
-import { parseDotEnv } from "../utilities/parseDotEnv";
+import { parseDotEnv } from "../utilities/parseSecrets";
import guidGenerator from "../utilities/randomId";
interface DropZoneProps {
diff --git a/frontend/src/components/utilities/parseJson.ts b/frontend/src/components/utilities/parseJson.ts
deleted file mode 100644
index e6eb36f80..000000000
--- a/frontend/src/components/utilities/parseJson.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-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/components/utilities/parseDotEnv.ts b/frontend/src/components/utilities/parseSecrets.ts
similarity index 81%
rename from frontend/src/components/utilities/parseDotEnv.ts
rename to frontend/src/components/utilities/parseSecrets.ts
index ac9c616b5..d1df7cd68 100644
--- a/frontend/src/components/utilities/parseDotEnv.ts
+++ b/frontend/src/components/utilities/parseSecrets.ts
@@ -65,3 +65,15 @@ export function parseDotEnv(src: ArrayBuffer | string) {
return object;
}
+
+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/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
index da4be8301..718bf7f1c 100644
--- a/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
+++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
@@ -6,8 +6,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { ProjectPermissionCan } from "@app/components/permissions";
-import { parseDotEnv } from "@app/components/utilities/parseDotEnv";
-import { parseJson } from "@app/components/utilities/parseJson";
+import { parseDotEnv, parseJson } from "@app/components/utilities/parseSecrets";
import {
Button,
FormControl,
@@ -111,7 +110,7 @@ export const PasteSecretEnvModal = ({
variant="star"
size={isSmaller ? "xs" : "sm"}
>
- Paste Secret Values
+ Paste Secrets
)}
diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx
index 017fce7fa..f105a08a5 100644
--- a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx
+++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx
@@ -9,8 +9,7 @@ import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality
-import { parseDotEnv } from "@app/components/utilities/parseDotEnv";
-import { parseJson } from "@app/components/utilities/parseJson";
+import { parseDotEnv, parseJson } from "@app/components/utilities/parseSecrets";
import { Button, Modal, ModalContent } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks";
From c8109b4e8439033ad5f66f5d0ab407a7c3a58d01 Mon Sep 17 00:00:00 2001
From: Scott Wilson
Date: Tue, 12 Nov 2024 16:46:35 -0800
Subject: [PATCH 22/36] improvement: add example paste value formats
---
.../components/v2/FormControl/FormControl.tsx | 5 ++-
.../SecretDropzone/PasteSecretEnvModal.tsx | 34 ++++++++++++++++++-
2 files changed, 37 insertions(+), 2 deletions(-)
diff --git a/frontend/src/components/v2/FormControl/FormControl.tsx b/frontend/src/components/v2/FormControl/FormControl.tsx
index 45f3f9cd1..8651422a1 100644
--- a/frontend/src/components/v2/FormControl/FormControl.tsx
+++ b/frontend/src/components/v2/FormControl/FormControl.tsx
@@ -83,6 +83,7 @@ export type FormControlProps = {
className?: string;
icon?: ReactNode;
tooltipText?: ReactElement | string;
+ tooltipClassName?: string;
};
export const FormControl = ({
@@ -96,7 +97,8 @@ export const FormControl = ({
isError,
icon,
className,
- tooltipText
+ tooltipText,
+ tooltipClassName
}: FormControlProps): JSX.Element => {
return (
@@ -108,6 +110,7 @@ export const FormControl = ({
id={id}
icon={icon}
tooltipText={tooltipText}
+ tooltipClassName={tooltipClassName}
/>
) : (
label
diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
index 718bf7f1c..aecb6c8a4 100644
--- a/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
+++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/PasteSecretEnvModal.tsx
@@ -1,6 +1,6 @@
import { useForm } from "react-hook-form";
import { subject } from "@casl/ability";
-import { faPaste } from "@fortawesome/free-solid-svg-icons";
+import { faInfoCircle, faPaste } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
@@ -67,6 +67,38 @@ const PasteEnvForm = ({ onParsedEnv }: Pick
) => {
label="Secret Values"
isError={Boolean(errors.value)}
errorText={errors.value?.message}
+ icon={ }
+ tooltipClassName="max-w-lg px-2 whitespace-pre-line"
+ tooltipText={
+
+
Example Formats:
+
+ {/* eslint-disable-next-line react/jsx-no-comment-textnodes */}
+ // .json
+ {JSON.stringify(
+ {
+ APP_NAME: "example-service",
+ APP_VERSION: "1.2.3",
+ NODE_ENV: "production"
+ },
+ null,
+ 2
+ )}
+
+
+ # .env
+ APP_NAME="example-service"
+ APP_VERSION="1.2.3"
+ NODE_ENV="production"
+
+
+ # .yml
+ APP_NAME: example-service
+ APP_VERSION: 1.2.3
+ NODE_ENV: production
+
+
+ }
>
Date: Thu, 14 Nov 2024 06:13:14 +0400
Subject: [PATCH 23/36] fix: cli npm release windows and symlink bugs
---
npm/package-lock.json | 31 ++++++++++++++++-
npm/package.json | 7 ++--
npm/src/index.cjs | 79 +++++++++++++++++++++++++++++++++++--------
3 files changed, 98 insertions(+), 19 deletions(-)
diff --git a/npm/package-lock.json b/npm/package-lock.json
index 30ea704bd..0c3dea6ef 100644
--- a/npm/package-lock.json
+++ b/npm/package-lock.json
@@ -9,12 +9,22 @@
"version": "0.0.0",
"hasInstallScript": true,
"dependencies": {
- "tar": "^6.2.0"
+ "tar": "^6.2.0",
+ "yauzl": "^3.2.0"
},
"bin": {
"infisical": "bin/infisical"
}
},
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
@@ -87,6 +97,12 @@
"node": ">=10"
}
},
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "license": "MIT"
+ },
"node_modules/tar": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
@@ -107,6 +123,19 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "node_modules/yauzl": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz",
+ "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "pend": "~1.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
}
}
}
diff --git a/npm/package.json b/npm/package.json
index c7b2ee7d3..0b9d0bbaf 100644
--- a/npm/package.json
+++ b/npm/package.json
@@ -8,7 +8,7 @@
"command-line"
],
"bin": {
- "infisical": "bin/infisical"
+ "infisical": "./bin/infisical"
},
"repository": {
"type": "git",
@@ -16,9 +16,10 @@
},
"author": "Infisical Inc, ",
"scripts": {
- "postinstall": "node src/index.cjs"
+ "preinstall": "node src/index.cjs"
},
"dependencies": {
- "tar": "^6.2.0"
+ "tar": "^6.2.0",
+ "yauzl": "^3.2.0"
}
}
diff --git a/npm/src/index.cjs b/npm/src/index.cjs
index 28fe0979b..398b3501a 100644
--- a/npm/src/index.cjs
+++ b/npm/src/index.cjs
@@ -4,13 +4,20 @@ const stream = require("node:stream");
const tar = require("tar");
const path = require("path");
const zlib = require("zlib");
+const yauzl = require("yauzl");
+
const packageJSON = require("../package.json");
-const supportedPlatforms = ["linux", "darwin", "win32", "freebsd"];
+const supportedPlatforms = ["linux", "darwin", "win32", "freebsd", "windows"];
const outputDir = "bin";
const getPlatform = () => {
- const platform = process.platform;
+ let platform = process.platform;
+
+ if (platform === "win32") {
+ platform = "windows";
+ }
+
if (!supportedPlatforms.includes(platform)) {
console.error("Your platform doesn't seem to be of type darwin, linux or windows");
process.exit(1);
@@ -53,12 +60,47 @@ const getArchitecture = () => {
return arch;
};
+async function extractZip(buffer, targetPath) {
+ return new Promise((resolve, reject) => {
+ yauzl.fromBuffer(buffer, { lazyEntries: true }, (err, zipfile) => {
+ if (err) return reject(err);
+
+ zipfile.readEntry();
+ zipfile.on("entry", entry => {
+ const isExecutable = entry.fileName === "infisical" || entry.fileName === "infisical.exe";
+
+ if (/\/$/.test(entry.fileName) || !isExecutable) {
+ // Directory entry
+ zipfile.readEntry();
+ } else {
+ // File entry
+ zipfile.openReadStream(entry, (err, readStream) => {
+ if (err) return reject(err);
+
+ const outputPath = path.join(targetPath, entry.fileName.includes("infisical") ? "infisical" : entry.fileName);
+ const writeStream = fs.createWriteStream(outputPath);
+
+ readStream.pipe(writeStream);
+ writeStream.on("close", () => {
+ zipfile.readEntry();
+ });
+ });
+ }
+ });
+
+ zipfile.on("end", resolve);
+ zipfile.on("error", reject);
+ });
+ });
+}
+
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`;
+ const EXTENSION = PLATFORM === "windows" ? "zip" : "tar.gz";
+ const downloadLink = `https://github.com/Infisical/infisical/releases/download/infisical-cli/${LATEST_RELEASE_VERSION}/infisical_${NUMERIC_RELEASE_VERSION}_${PLATFORM}_${ARCH}.${EXTENSION}`;
// Ensure the output directory exists
if (!fs.existsSync(outputDir)) {
@@ -77,19 +119,26 @@ async function main() {
throw new Error(`Failed to fetch: ${response.status} - ${response.statusText}`);
}
- 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"
- })
- );
+ if (EXTENSION === "zip") {
+ // For ZIP files, we need to buffer the whole thing first
+ const buffer = await response.arrayBuffer();
+ await extractZip(Buffer.from(buffer), outputDir);
+ } else {
+ // For tar.gz files, we stream
+ 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);
- });
+ outStream.on("error", reject);
+ outStream.on("close", resolve);
+ });
+ }
// Give the binary execute permissions if we're not on Windows
if (PLATFORM !== "win32") {
From fd77708cad14d89c2d959f8dc38d8294aaa8f45a Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Thu, 14 Nov 2024 02:02:23 -0700
Subject: [PATCH 24/36] add docs for linux ha
---
docs/mint.json | 5 +-
.../native/high-availability.mdx | 520 ------------------
.../reference-architectures/aws-ecs.mdx | 2 +-
.../linux-deployment-ha.mdx | 383 +++++++++++++
4 files changed, 388 insertions(+), 522 deletions(-)
delete mode 100644 docs/self-hosting/deployment-options/native/high-availability.mdx
create mode 100644 docs/self-hosting/reference-architectures/linux-deployment-ha.mdx
diff --git a/docs/mint.json b/docs/mint.json
index fae4ade1a..865af4324 100644
--- a/docs/mint.json
+++ b/docs/mint.json
@@ -292,7 +292,10 @@
},
{
"group": "Reference architectures",
- "pages": ["self-hosting/reference-architectures/aws-ecs"]
+ "pages": [
+ "self-hosting/reference-architectures/aws-ecs",
+ "self-hosting/reference-architectures/linux-deployment-ha"
+ ]
},
"self-hosting/ee",
"self-hosting/faq"
diff --git a/docs/self-hosting/deployment-options/native/high-availability.mdx b/docs/self-hosting/deployment-options/native/high-availability.mdx
deleted file mode 100644
index 931acb4df..000000000
--- a/docs/self-hosting/deployment-options/native/high-availability.mdx
+++ /dev/null
@@ -1,520 +0,0 @@
----
-title: "Automatically deploy Infisical with High Availability"
-sidebarTitle: "High Availability"
----
-
-
-# Self-Hosting Infisical with a native High Availability (HA) deployment
-
-This page describes the Infisical architecture designed to provide high availability (HA) and how to deploy Infisical with high availability. The high availability deployment is designed to ensure that Infisical services are always available and can handle service failures gracefully, without causing service disruptions.
-
-
- This deployment option is currently only available for Debian-based nodes (e.g., Ubuntu, Debian).
- We plan on adding support for other operating systems in the future.
-
-
-## High availability architecture
-| Service | Nodes | Configuration | GCP | AWS |
-|----------------------------------|----------------|------------------------------|---------------|--------------|
-| External load balancer$^1$ | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5n.xlarge |
-| Internal load balancer$^2$ | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5n.xlarge |
-| Etcd cluster$^3$ | 3 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5n.xlarge |
-| PostgreSQL$^4$ | 3 | 2 vCPU, 7.5 GB memory | n1-standard-2 | m5.large |
-| Sentinel$^4$ | 3 | 2 vCPU, 7.5 GB memory | n1-standard-2 | m5.large |
-| Redis$^4$ | 3 | 2 vCPU, 7.5 GB memory | n1-standard-2 | m5.large |
-| Infisical Core | 3 | 8 vCPU, 7.2 GB memory | n1-highcpu-8 | c5.2xlarge |
-
-**Footnotes:**
-1. External load balancer: If you wish to have multiple instances of the internal load balancer, you will need to use an external load balancer to distribute incoming traffic across multiple internal load balancers.
- Using multiple internal load balancers is recommended for high-traffic environments. In the following guide we will use a single internal load balancer, as external load balancing falls outside the scope of this guide.
-2. Internal load balancer: The internal load balancer (a HAProxy instance) is used to distribute incoming traffic across multiple Infisical Core instances, Postgres nodes, and Redis nodes. The internal load balancer exposes a set of ports _(80 for Infiscial, 5000 for Read/Write postgres, 5001 for Read-only postgres, and 6379 for Redis)_. Where these ports route to is determained by the internal load balancer based on the availability and health of the service nodes.
- The internal load balancer is only accessible from within the same network, and is not exposed to the public internet.
-3. Etcd cluster: Etcd is a distributed key-value store used to store and distribute data between the PostgreSQL nodes. Etcd is dependent on high disk I/O performance, therefore it is highly recommended to use highly performant SSD disks for the Etcd nodes, with _at least_ 80GB of disk space.
-4. The Redis and PostgreSQL nodes will automatically be configured for high availability and used in your Infisical Core instances. However, you can optionally choose to bring your own database (BYOD), and skip these nodes. See more on how to [provide your own databases](#provide-your-own-databases).
-
-
- For all services that require multiple nodes, it is recommended to deploy them across multiple availability zones (AZs) to ensure high availability and fault tolerance. This will help prevent service disruptions in the event of an AZ failure.
-
-
-
-The image above shows how a high availability deployment of Infisical is structured. In this example, an external load balancer is used to distribute incoming traffic across multiple internal load balancers. The internal load balancers. The external load balancer isn't required, and it will require additional configuration to set up.
-
-### Fault Tolerance
-This setup provides N+1 redundancy, meaning it can tolerate the failure of any single node without service interruption.
-
-## Ansible
-### What is Ansible
-Ansible is an open-source automation tool that simplifies application deployment, configuration management, and task automation.
-At Infisical, we use Ansible to automate the deployment of Infisical services. The Ansible roles are designed to make it easy to deploy Infisical services in a high availability environment.
-
-### Installing Ansible
-
-
- ```bash
- pipx install --include-deps ansible
- ```
-
-
- ```bash
- ansible --version
- ```
-
-
-
-
-### Understanding Ansible Concepts
-
-* Inventory _(inventory.ini)_: A file that lists your target hosts.
-* Playbook _(playbook.yml)_: YAML file containing a set of tasks to be executed on hosts.
-* Roles: Reusable units of organization for playbooks. Roles are used to group tasks together in a structured and reusable manner.
-
-
-### Basic Ansible Commands
-Running a playbook with with an invetory file:
-```bash
- ansible-playbook -i inventory.ini playbook.yml
-```
-
-This is how you would run the playbook containing the roles for setting up Infisical in a high availability environment.
-
-### Installing the Infisical High Availability Deployment Ansible Role
-The Infisical Ansible role is available on Ansible Galaxy. You can install the role by running the following command:
-```bash
- ansible-galaxy collection install infisical.infisical_core_ha_deployment
-```
-
-
-## Set up components
-1. External load balancer (optional, and not covered in this guide)
-2. [Configure Etcd cluster](#configure-etcd-cluster)
-3. [Configure PostgreSQL database](#configure-postgresql-database)
-4. [Configure Redis/Sentinel](#configure-redis-and-sentinel)
-5. [Configure Infisical Core](#configure-infisical-core)
-
-
-The servers start on the same 52.1.0.0/24 private network range, and can connect to each other freely on these addresses.
-
-The following list includes descriptions of each server and its assigned IP:
-
-52.1.0.1: External Load Balancer
-52.1.0.2: Internal Load Balancer
-52.1.0.3: Etcd 1
-52.1.0.4: Etcd 2
-52.1.0.5: Etcd 3
-52.1.0.6: PostgreSQL 1
-52.1.0.7: PostgreSQL 2
-52.1.0.8: PostgreSQL 3
-52.1.0.9: Redis 1
-52.1.0.10: Redis 2
-52.1.0.11: Redis 3
-52.1.0.12: Sentinel 1
-52.1.0.13: Sentinel 2
-52.1.0.14: Sentinel 3
-52.1.0.15: Infisical Core 1
-52.1.0.16: Infisical Core 2
-52.1.0.17: Infisical Core 3
-
-
-
-### Configure Etcd cluster
-
-Configuring the ETCD cluster is the first step in setting up a high availability deployment of Infisical.
-The ETCD cluster is used to store and distribute data between the PostgreSQL nodes. The ETCD cluster is a distributed key-value store that is highly available and fault-tolerant.
-
-```yaml example.playbook.yml
- - hosts: all
- gather_facts: true
-
- - name: Set up etcd cluster
- hosts: etcd
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: etcd
-```
-
-```ini example.inventory.ini
- [etcd]
- etcd1 ansible_host=52.1.0.3
- etcd2 ansible_host=52.1.0.4
- etcd3 ansible_host=52.1.0.5
-
- [etcd:vars]
- ansible_user=ubuntu
- ansible_ssh_private_key_file=./ssh-key.pem
- ansible_ssh_common_args='-o StrictHostKeyChecking=no'
-```
-
-### Configure PostgreSQL database
-
-The Postgres role takes a set of parameters that are used to configure your PostgreSQL database.
-
-Make sure to set the following variables in your playbook.yml file:
-- `postgres_super_user_password`: The password for the 'postgres' database user.
-- `postgres_db_name`: The name of the database that will be created on the leader node and replicated to the secondary nodes.
-- `postgres_user`: The name of the user that will be created on the leader node and replicated to the secondary nodes.
-- `postgres_user_password`: The password for the user that will be created on the leader node and replicated to the secondary nodes.
-- `etcd_hosts`: The list of etcd hosts that the PostgreSQL nodes will use to communicate with etcd. By default you want to keep this value set to `"{{ groups['etcd'] }}"`
-
-```yaml example.playbook.yml
- - hosts: all
- gather_facts: true
-
- - name: Set up PostgreSQL with Patroni
- hosts: postgres
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: postgres
- vars:
- postgres_super_user_password: "your-super-user-password"
- postgres_user: infisical-user
- postgres_user_password: "your-password"
- postgres_db_name: infisical-db
-
- etcd_hosts: "{{ groups['etcd'] }}"
-```
-
-```ini example.inventory.ini
- [postgres]
- postgres1 ansible_host=52.1.0.6
- postgres2 ansible_host=52.1.0.7
- postgres3 ansible_host=52.1.0.8
-```
-
-### Configure Redis and Sentinel
-
-The Redis role takes a single variable as input, which is the redis password.
-The Sentinel and Redis hosts will run the same role, therefore we are running the task for both the sentinel and redis hosts, `hosts: redis:sentinel`.
-
-- `redis_password`: The password that will be set for the Redis instance.
-
-```yaml example.playbook.yml
- - hosts: all
- gather_facts: true
-
- - name: Setup Redis and Sentinel
- hosts: redis:sentinel
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: redis
- vars:
- redis_password: "REDIS_PASSWORD"
-```
-
-```ini example.inventory.ini
- [redis]
- redis1 ansible_host=52.1.0.9
- redis2 ansible_host=52.1.0.10
- redis3 ansible_host=52.1.0.11
-
- [sentinel]
- sentinel1 ansible_host=52.1.0.12
- sentinel2 ansible_host=52.1.0.13
- sentinel3 ansible_host=52.1.0.14
-```
-
-### Configure Internal Load Balancer
-
-The internal load balancer used is HAProxy. HAProxy will expose a set of ports as listed below. Each port will route to a different service based on the availability and health of the service nodes.
-
-- Port 80: Infisical Core
-- Port 5000: Read/Write PostgreSQL
-- Port 5001: Read-only PostgreSQL
-- Port 6379: Redis
-- Port 7000: HAProxy monitoring
-These ports will need to be exposed on your network to become accessible from the outside world.
-
-The HAProxy configuration file is generated by the Infisical Core role, and is located at `/etc/haproxy/haproxy.cfg` on your internal load balancer node.
-
-The HAProxy setup comes with a monitoring panel. You have to set the username/password combination for the monitoring panel by setting the `stats_user` and `stats_password` variables in the HAProxy role.
-
-
-Once the HAProxy role has fully executed, you can monitor your HA setup by navigating to `http://52.1.0.2:7000/haproxy?stats` in your browser.
-
-```ini example.inventory.ini
-[haproxy]
-internal_lb ansible_host=52.1.0.2
-```
-
-```yaml example.playbook.yml
-- name: Set up HAProxy
- hosts: haproxy
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: haproxy
- vars:
- stats_user: "stats-username"
- stats_password: "stats-password!"
-
- postgres_servers: "{{ groups['postgres'] }}"
- infisical_servers: "{{ groups['infisical'] }}"
- redis_servers: "{{ groups['redis'] }}"
-```
-
-
-
-### Configure Infisical Core
-
-The Infisical Core role will set up your actual Infisical instances.
-
-The `env_vars` variable is used to set the environment variables that Infisical will use. The minimum required environment variables are `ENCRYPTION_KEY` and `AUTH_SECRET`. You can find a list of all available environment variables [here](/docs/self-hosting/configuration/envars#general-platform).
-The `DB_CONNECTION_URI` and `REDIS_URL` variables will automatically be set if you're running the full playbook. However, you can choose to set them yourself, and skip the Postgres, etcd, redis/sentinel roles entirely.
-
-
- If you later need to add new environment varibles to your Infisical deployments, it's important you add the variables to **all** your Infisical nodes.
- You can find the environment file for Infisical at `/etc/infisical/environment`.
- After editing the environment file, you need to reload the Infisical service by doing `systemctl restart infisical`.
-
-
-```yaml example.playbook.yml
- - hosts: all
- gather_facts: true
-
- - name: Setup Infisical
- hosts: infisical
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: infisical
- env_vars:
- ENCRYPTION_KEY: "YOUR_ENCRYPTION_KEY" # openssl rand -hex 16
- AUTH_SECRET: "YOUR_AUTH_SECRET" # openssl rand -base64 32
-```
-
-```ini example.inventory.ini
- [infisical]
- infisical1 ansible_host=52.1.0.15
- infisical2 ansible_host=52.1.0.16
- infisical3 ansible_host=52.1.0.17
-```
-
-## Provide your own databases
-Bringing your own database is an option using the Infisical Core deployment role.
-By bringing your own database, you're able to skip the Etcd, Postgres, and Redis/Sentinel roles entirely.
-
-To bring your own database, you need to set the `DB_CONNECTION_URI` and `REDIS_URL` environment variables in the Infisical Core role.
-
-```yaml example.playbook.yml
- - hosts: all
- gather_facts: true
-
- - name: Setup Infisical
- hosts: infisical
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: infisical
- env_vars:
- ENCRYPTION_KEY: "YOUR_ENCRYPTION_KEY" # openssl rand -hex 16
- AUTH_SECRET: "YOUR_AUTH_SECRET" # openssl rand -base64 32
- DB_CONNECTION_URI: "postgres://user:password@localhost:5432/infisical"
- REDIS_URL: "redis://localhost:6379"
-```
-
-```ini example.inventory.ini
- [infisical]
- infisical1 ansible_host=52.1.0.15
- infisical2 ansible_host=52.1.0.16
- infisical3 ansible_host=52.1.0.17
-```
-
-## Full deployment example
-To make it easier to get started, we've provided a full deployment example that you can use to deploy Infisical in a high availability environment.
-
-- This deployment does not use an external load balancer.
-- You **must** change the environment variables defined in the `playbook.yml` example.
-- You have update the IP addresses in the `inventory.ini` file to match your own network configuration.
-- You need to set the SSH key and ssh user in the `inventory.ini` file.
-
-
-
- Install Ansible using the pipx Python package manager.
- ```bash
- pipx install --include-deps ansible
- ```
-
-
-
- Install the Infisical deployment role from Ansible Galaxy.
- ```bash
- ansible-galaxy collection install infisical.infisical_core_ha_deployment
- ```
-
-
-
- Create an `inventory.ini` file, and define your hosts and their IP addresses. You can use the example below as a template, and update the IP addresses to match your own network configuration.
- Make sure to set the SSH key and ssh user in the `inventory.ini` file. Please see the example below.
-
- ```ini example.inventory.ini
- [etcd]
- etcd1 ansible_host=52.1.0.3
- etcd2 ansible_host=52.1.0.4
- etcd3 ansible_host=52.1.0.5
-
- [postgres]
- postgres1 ansible_host=52.1.0.6
- postgres2 ansible_host=52.1.0.7
- postgres3 ansible_host=52.1.0.8
-
- [infisical]
- infisical1 ansible_host=52.1.0.15
- infisical2 ansible_host=52.1.0.16
- infisical3 ansible_host=52.1.0.17
-
- [redis]
- redis1 ansible_host=52.1.0.9
- redis2 ansible_host=52.1.0.10
- redis3 ansible_host=52.1.0.11
-
- [sentinel]
- sentinel1 ansible_host=52.1.0.12
- sentinel2 ansible_host=52.1.0.13
- sentinel3 ansible_host=52.1.0.14
-
- [haproxy]
- internal_lb ansible_host=52.1.0.2
-
- ; This can be defined individually for each host, or globally for all hosts.
- ; In this case the credentials are the same for all hosts, so we define them globally as seen below ([all:vars]).
- [all:vars]
- ansible_user=ubuntu
- ansible_ssh_private_key_file=./your-ssh-key.pem
- ansible_ssh_common_args='-o StrictHostKeyChecking=no'
- ```
-
-
- The Ansible playbook is where you define which roles/tasks to execute on which hosts.
-
- ```yaml example.playbook.yml
- ---
- # Important, we must gather facts from all hosts prior to running the roles to ensure we have all the information we need.
- - hosts: all
- gather_facts: true
-
- - name: Set up etcd cluster
- hosts: etcd
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: etcd
-
- - name: Set up PostgreSQL with Patroni
- hosts: postgres
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: postgres
- vars:
- postgres_super_user_password: "" # Password for the 'postgres' database user
-
- # A database with these credentials will be created on the leader node, and replicated to the secondary nodes.
- postgres_db_name:
- postgres_user:
- postgres_user_password:
-
- etcd_hosts: "{{ groups['etcd'] }}"
-
- - name: Setup Redis and Sentinel
- hosts: redis:sentinel
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: redis
- vars:
- redis_password: ""
-
- - name: Set up HAProxy
- hosts: haproxy
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: haproxy
- vars:
- stats_user: ""
- stats_password: ""
-
- postgres_servers: "{{ groups['postgres'] }}"
- infisical_servers: "{{ groups['infisical'] }}"
- redis_servers: "{{ groups['redis'] }}"
- - name: Setup Infisical
- hosts: infisical
- become: true
- collections:
- - infisical.infisical_core_ha_deployment
- roles:
- - role: infisical
- env_vars:
- ENCRYPTION_KEY: "YOUR_ENCRYPTION_KEY" # openssl rand -hex 16
- AUTH_SECRET: "YOUR_AUTH_SECRET" # openssl rand -base64 32
- ```
-
-
- After creating the `playbook.yml` and `inventory.ini` files, you can run the playbook using the following command
- ```bash
- ansible-playbook -i inventory.ini playbook.yml
- ```
-
- This step may take upwards of 10 minutes to complete, depending on the number of nodes and the network speed.
- Once the playbook has completed, you should have a fully deployed high availability Infisical environment.
-
- To access Infisical, you can try navigating to `http://52.1.0.2`, in order to view your newly deployed Infisical instance.
-
-
-
-
-## Post-deployment steps
-After deploying Infisical in a high availability environment, you should perform the following post-deployment steps:
-- Check your deployment to ensure that all services are running as expected. You can use the HAProxy monitoring panel to check the status of your services (http://52.1.0.2:7000/haproxy?stats)
-- Attempt to access the Infisical Core instances to ensure that they are accessible from the internal load balancer. (http://52.1.0.2)
-
-A HAProxy stats page indicating success will look like the image below
-
-
-
-## Security Considerations
-### Network Security
-Secure the network that your instances run on. While this falls outside the scope of Infisical deployment, it's crucial for overall security.
-AWS-specific recommendations:
-
-Use Virtual Private Cloud (VPC) to isolate your infrastructure.
-Configure security groups to restrict inbound and outbound traffic.
-Use Network Access Control Lists (NACLs) for additional network-level security.
-
-
- Please take note that the Infisical team cannot provide infrastructure support for **free self-hosted** deployments. If you need help with infrastructure, we recommend upgrading to a [paid plan](https://infisical.com/pricing) which includes infrastructure support.
-
- You can also join our community [Slack](https://infisical.com/slack) for help and support from the community.
-
-
-
-### Troubleshooting
-
- If you encounter this issue, please update your ansible config (`ansible.cfg`) file with the following configuration:
- ```ini
- [defaults]
- allow_world_readable_tmpfiles = true
- ```
-
- You can read more about the solution [here](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/sh_shell.html#parameter-world_readable_temp)
-
-
-
- This issue can be caused by a number of reasons, mostly realted to the network configuration. Here are a few things you can check:
- 1. Ensure that the firewall is not blocking the connection. You can check this by running `ufw status`. Ensure that port 80 is open.
- 2. If you're using a cloud provider like AWS or GCP, ensure that the security group allows traffic on port 80.
- 3. Ensure that the HAProxy service is running. You can check this by running `systemctl status haproxy`.
- 4. Ensure that the Infisical service is running. You can check this by running `systemctl status infisical`.
-
\ No newline at end of file
diff --git a/docs/self-hosting/reference-architectures/aws-ecs.mdx b/docs/self-hosting/reference-architectures/aws-ecs.mdx
index a4ce4a2b6..5a71c9572 100644
--- a/docs/self-hosting/reference-architectures/aws-ecs.mdx
+++ b/docs/self-hosting/reference-architectures/aws-ecs.mdx
@@ -1,5 +1,5 @@
---
-title: "AWS ECS"
+title: "AWS ECS (HA)"
description: "Reference architecture for self-hosting Infisical on AWS ECS"
---
diff --git a/docs/self-hosting/reference-architectures/linux-deployment-ha.mdx b/docs/self-hosting/reference-architectures/linux-deployment-ha.mdx
new file mode 100644
index 000000000..7e4240016
--- /dev/null
+++ b/docs/self-hosting/reference-architectures/linux-deployment-ha.mdx
@@ -0,0 +1,383 @@
+---
+title: "Linux (HA)"
+description: "Infisical High Availability Deployment architecture for Linux"
+---
+
+This guide describes how to achieve a highly available deployment of Infisical on Linux machines without containerization. The architecture provided serves as a foundation for minimum high availability, which you can scale based on your specific requirements.
+
+## Architecture Overview
+
+
+
+The deployment consists of the following key components:
+
+| Service | Nodes | Recommended Specs | GCP Instance | AWS Instance |
+|---------------------------|-------|---------------------------|-----------------|--------------|
+| External Load Balancer | 1 | 4 vCPU, 4 GB memory | n1-highcpu-4 | c5n.xlarge |
+| Internal Load Balancer | 1 | 4 vCPU, 4 GB memory | n1-highcpu-4 | c5n.xlarge |
+| Etcd Cluster | 3 | 4 vCPU, 4 GB memory | n1-highcpu-4 | c5n.xlarge |
+| PostgreSQL Cluster | 3 | 2 vCPU, 8 GB memory | n1-standard-2 | m5.large |
+| Redis + Sentinel | 3+3 | 2 vCPU, 8 GB memory | n1-standard-2 | m5.large |
+| Infisical Core | 3 | 2 vCPU, 4 GB memory | n1-highcpu-2 | c5.large |
+
+### Network Architecture
+
+All servers operate within the 52.1.0.0/24 private network range with the following IP assignments:
+
+| Service | IP Address |
+|----------------------|------------|
+| External Load Balancer| 52.1.0.1 |
+| Internal Load Balancer| 52.1.0.2 |
+| Etcd Node 1 | 52.1.0.3 |
+| Etcd Node 2 | 52.1.0.4 |
+| Etcd Node 3 | 52.1.0.5 |
+| PostgreSQL Node 1 | 52.1.0.6 |
+| PostgreSQL Node 2 | 52.1.0.7 |
+| PostgreSQL Node 3 | 52.1.0.8 |
+| Redis Node 1 | 52.1.0.9 |
+| Redis Node 2 | 52.1.0.10 |
+| Redis Node 3 | 52.1.0.11 |
+| Sentinel Node 1 | 52.1.0.12 |
+| Sentinel Node 2 | 52.1.0.13 |
+| Sentinel Node 3 | 52.1.0.14 |
+| Infisical Core 1 | 52.1.0.15 |
+| Infisical Core 2 | 52.1.0.16 |
+| Infisical Core 3 | 52.1.0.17 |
+
+## Component Setup Guide
+
+### 1. Configure Etcd Cluster
+
+The Etcd cluster is needed for leader election in the PostgreSQL HA setup. Skip this step if using managed PostgreSQL.
+
+1. Install Etcd on each node:
+```bash
+sudo apt update
+sudo apt install etcd
+```
+
+2. Configure each node with unique identifiers and cluster membership. Example configuration for Node 1 (`/etc/etcd/etcd.conf`):
+```yaml
+name: etcd1
+data-dir: /var/lib/etcd
+initial-cluster-state: new
+initial-cluster-token: etcd-cluster-1
+initial-cluster: etcd1=http://52.1.0.3:2380,etcd2=http://52.1.0.4:2380,etcd3=http://52.1.0.5:2380
+initial-advertise-peer-urls: http://52.1.0.3:2380
+listen-peer-urls: http://52.1.0.3:2380
+listen-client-urls: http://52.1.0.3:2379,http://127.0.0.1:2379
+advertise-client-urls: http://52.1.0.3:2379
+```
+
+### 2. Configure PostgreSQL
+
+For production deployments, you have two options for highly available PostgreSQL:
+
+#### Option A: Managed PostgreSQL Service (Recommended for Most Users)
+
+Use cloud provider managed services:
+- AWS: Amazon RDS for PostgreSQL with Multi-AZ
+- GCP: Cloud SQL for PostgreSQL with HA configuration
+- Azure: Azure Database for PostgreSQL with zone redundant HA
+
+These services handle replication, failover, and maintenance automatically.
+
+#### Option B: Self-Managed PostgreSQL Cluster
+
+Full HA installation guide of PostgreSQL is beyond the scope of this document. However, we have provided an overview of resources and code snippets below to guide your deployment.
+
+1. Required Components:
+ - PostgreSQL 14+ on each node
+ - Patroni for cluster management
+ - Etcd for distributed consensus
+
+2. Documentation we recommend you read:
+ - [Complete Patroni Setup Guide](https://patroni.readthedocs.io/en/latest/README.html)
+ - [PostgreSQL Replication Documentation](https://www.postgresql.org/docs/current/high-availability.html)
+
+3. Key Steps Overview:
+```bash
+# 1. Install requirements on each PostgreSQL node
+sudo apt update
+sudo apt install -y postgresql-14 postgresql-contrib-14 python3-pip
+pip3 install patroni[etcd] psycopg2-binary
+
+# 2. Create Patroni config directory
+sudo mkdir /etc/patroni
+sudo chown postgres:postgres /etc/patroni
+
+# 3. Create Patroni configuration (example for first node)
+# /etc/patroni/config.yml - REQUIRES CAREFUL CUSTOMIZATION
+```
+
+```yaml
+scope: infisical-cluster
+namespace: /db/
+name: postgresql1
+
+restapi:
+ listen: 52.1.0.6:8008
+ connect_address: 52.1.0.6:8008
+
+etcd:
+ hosts: 52.1.0.3:2379,52.1.0.4:2379,52.1.0.5:2379
+
+bootstrap:
+ dcs:
+ ttl: 30
+ loop_wait: 10
+ retry_timeout: 10
+ maximum_lag_on_failover: 1048576
+ postgresql:
+ use_pg_rewind: true
+ parameters:
+ max_connections: 1000
+ shared_buffers: 2GB
+ work_mem: 8MB
+ max_worker_processes: 8
+ max_parallel_workers_per_gather: 4
+ max_parallel_workers: 8
+ wal_level: replica
+ hot_standby: "on"
+ max_wal_senders: 10
+ max_replication_slots: 10
+ hot_standby_feedback: "on"
+```
+
+4. Important considerations:
+ - Proper disk configuration for WAL and data directories
+ - Network latency between nodes
+ - Backup strategy and point-in-time recovery
+ - Monitoring and alerting setup
+ - Connection pooling configuration
+ - Security and network access controls
+
+5. Recommended readings:
+ - [PostgreSQL Backup and Recovery](https://www.postgresql.org/docs/current/backup.html)
+ - [PostgreSQL Monitoring](https://www.postgresql.org/docs/current/monitoring.html)
+
+### 3. Configure Redis and Sentinel
+
+Similar to PostgreSQL, a full HA Redis setup guide is beyond the scope of this document. Below are the key resources and considerations for your deployment.
+
+#### Option A: Managed Redis Service (Recommended for Most Users)
+
+Use cloud provider managed Redis services:
+- AWS: ElastiCache for Redis with Multi-AZ
+- GCP: Memorystore for Redis with HA
+- Azure: Azure Cache for Redis with zone redundancy
+
+Follow your cloud provider's documentation:
+- [AWS ElastiCache Documentation](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/WhatIs.html)
+- [GCP Memorystore Documentation](https://cloud.google.com/memorystore/docs/redis)
+- [Azure Redis Cache Documentation](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/)
+
+#### Option B: Self-Managed Redis Cluster
+
+Setting up a production Redis HA cluster requires understanding several components. Refer to these linked resources:
+
+1. Required Reading:
+ - [Redis Sentinel Documentation](https://redis.io/docs/management/sentinel/)
+ - [Redis Replication Guide](https://redis.io/topics/replication)
+ - [Redis Security Guide](https://redis.io/topics/security)
+
+2. Key Steps Overview:
+```bash
+# 1. Install Redis on all nodes
+sudo apt update
+sudo apt install redis-server
+
+# 2. Configure master node (52.1.0.9)
+# /etc/redis/redis.conf
+```
+
+```conf
+bind 52.1.0.9
+port 6379
+dir /var/lib/redis
+maxmemory 3gb
+maxmemory-policy noeviction
+requirepass "your_redis_password"
+masterauth "your_redis_password"
+```
+
+3. Configure replica nodes (`52.1.0.10`, `52.1.0.11`):
+```conf
+bind 52.1.0.10 # Change for each replica
+port 6379
+dir /var/lib/redis
+replicaof 52.1.0.9 6379
+masterauth "your_redis_password"
+requirepass "your_redis_password"
+```
+
+4. Configure Sentinel nodes (`52.1.0.12`, `52.1.0.13`, `52.1.0.14`):
+```conf
+port 26379
+sentinel monitor mymaster 52.1.0.9 6379 2
+sentinel auth-pass mymaster "your_redis_password"
+sentinel down-after-milliseconds mymaster 5000
+sentinel failover-timeout mymaster 60000
+sentinel parallel-syncs mymaster 1
+```
+
+5. Recommended Additional Reading:
+ - [Redis High Availability Tools](https://redis.io/topics/high-availability)
+ - [Redis Sentinel Client Implementation](https://redis.io/topics/sentinel-clients)
+
+### 4. Configure HAProxy Load Balancer
+
+Install and configure HAProxy for internal load balancing:
+
+```conf ha-proxy-config
+global
+ maxconn 10000
+ log stdout format raw local0
+
+defaults
+ log global
+ mode tcp
+ retries 3
+ timeout client 30m
+ timeout connect 10s
+ timeout server 30m
+ timeout check 5s
+
+listen stats
+ mode http
+ bind *:7000
+ stats enable
+ stats uri /
+
+resolvers hostdns
+ nameserver dns 127.0.0.11:53
+ resolve_retries 3
+ timeout resolve 1s
+ timeout retry 1s
+ hold valid 5s
+
+frontend postgres_master
+ bind *:5000
+ default_backend postgres_master_backend
+
+frontend postgres_replicas
+ bind *:5001
+ default_backend postgres_replica_backend
+
+backend postgres_master_backend
+ option httpchk GET /master
+ http-check expect status 200
+ default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
+ server postgres-1 52.1.0.6:5432 check port 8008
+ server postgres-2 52.1.0.7:5432 check port 8008
+ server postgres-3 52.1.0.8:5432 check port 8008
+
+backend postgres_replica_backend
+ option httpchk GET /replica
+ http-check expect status 200
+ default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
+ server postgres-1 52.1.0.6:5432 check port 8008
+ server postgres-2 52.1.0.7:5432 check port 8008
+ server postgres-3 52.1.0.8:5432 check port 8008
+
+frontend redis_master_frontend
+ bind *:6379
+ default_backend redis_master_backend
+
+backend redis_master_backend
+ option tcp-check
+ tcp-check send AUTH\ 123456\r\n
+ tcp-check expect string +OK
+ tcp-check send PING\r\n
+ tcp-check expect string +PONG
+ tcp-check send info\ replication\r\n
+ tcp-check expect string role:master
+ tcp-check send QUIT\r\n
+ tcp-check expect string +OK
+ server redis-1 52.1.0.9:6379 check inter 1s
+ server redis-2 52.1.0.10:6379 check inter 1s
+ server redis-3 52.1.0.11:6379 check inter 1s
+
+frontend infisical_frontend
+ bind *:80
+ default_backend infisical_backend
+
+backend infisical_backend
+ option httpchk GET /api/status
+ http-check expect status 200
+ server infisical-1 52.1.0.15:8080 check inter 1s
+ server infisical-2 52.1.0.16:8080 check inter 1s
+ server infisical-3 52.1.0.17:8080 check inter 1s
+```
+
+### 5. Deploy Infisical Core
+
+
+ First, add the Infisical repository:
+ ```bash
+ curl -1sLf \
+ 'https://dl.cloudsmith.io/public/infisical/infisical-core/setup.deb.sh' \
+ | sudo -E bash
+ ```
+
+ Then install Infisical:
+ ```bash
+ sudo apt-get update && sudo apt-get install -y infisical-core
+ ```
+
+
+ For production environments, we strongly recommend installing a specific version of the package to maintain consistency across reinstalls. View available versions at [Infisical Package Versions](https://cloudsmith.io/~infisical/repos/infisical-core/packages/).
+
+
+
+
+ First, add the Infisical repository:
+ ```bash
+ curl -1sLf \
+ 'https://dl.cloudsmith.io/public/infisical/infisical-core/setup.rpm.sh' \
+ | sudo -E bash
+ ```
+
+ Then install Infisical:
+ ```bash
+ sudo yum install infisical-core
+ ```
+
+
+ For production environments, we strongly recommend installing a specific version of the package to maintain consistency across reinstalls. View available versions at [Infisical Package Versions](https://cloudsmith.io/~infisical/repos/infisical-core/packages/).
+
+
+
+
+
+Next, create configuration file `/etc/infisical/infisical.rb` with the following:
+
+```ruby
+infisical_core['ENCRYPTION_KEY'] = 'your-secure-encryption-key'
+infisical_core['AUTH_SECRET'] = 'your-secure-auth-secret'
+
+infisical_core['DB_CONNECTION_URI'] = 'postgres://user:pass@52.1.0.2:5000/infisical'
+infisical_core['REDIS_URL'] = 'redis://52.1.0.2:6379'
+
+infisical_core['PORT'] = 8080
+```
+
+To generate `ENCRYPTION_KEY` and `AUTH_SECRET` view the [following configurations documentation here](/self-hosting/configuration/envars).
+
+If you are using managed services for either Postgres or Redis, please replace the values of the secrets accordingly.
+
+
+Lastly, start and verify each node running infisical-core:
+```bash
+sudo infisical-ctl reconfigure
+sudo infisical-ctl status
+```
+
+## Monitoring and Maintenance
+
+1. Monitor HAProxy stats: `http://52.1.0.2:7000/haproxy?stats`
+2. Monitor Infisical logs: `sudo infisical-ctl tail`
+3. Check cluster health:
+ - Etcd: `etcdctl cluster-health`
+ - PostgreSQL: `patronictl list`
+ - Redis: `redis-cli info replication`
From 4ba389986114ced9e24db652f3ee945fdb49ec6c Mon Sep 17 00:00:00 2001
From: Sheen Capadngan
Date: Fri, 15 Nov 2024 01:07:36 +0800
Subject: [PATCH 25/36] doc: add docs for gitlab oidc auth
---
.../platform/identities/oidc-auth/gitlab.mdx | 145 ++++++++++++++++++
docs/mint.json | 3 +-
2 files changed, 147 insertions(+), 1 deletion(-)
create mode 100644 docs/documentation/platform/identities/oidc-auth/gitlab.mdx
diff --git a/docs/documentation/platform/identities/oidc-auth/gitlab.mdx b/docs/documentation/platform/identities/oidc-auth/gitlab.mdx
new file mode 100644
index 000000000..d6b2c8461
--- /dev/null
+++ b/docs/documentation/platform/identities/oidc-auth/gitlab.mdx
@@ -0,0 +1,145 @@
+---
+title: GitLab
+description: "Learn how to authenticate GitLab pipelines with Infisical using OpenID Connect (OIDC)."
+---
+
+**OIDC Auth** is a platform-agnostic JWT-based authentication method that can be used to authenticate from any platform or environment using an identity provider with OpenID Connect.
+
+## Diagram
+
+The following sequence diagram illustrates the OIDC Auth workflow for authenticating GitLab pipelines with Infisical.
+
+```mermaid
+sequenceDiagram
+ participant Client as GitLab Pipeline
+ participant Idp as Identity Provider
+ participant Infis as Infisical
+
+ Client->>Idp: Step 1: Request identity token
+ Idp-->>Client: Return JWT with verifiable claims
+
+ Note over Client,Infis: Step 2: Login Operation
+ Client->>Infis: Send signed JWT to /api/v1/auth/oidc-auth/login
+
+ Note over Infis,Idp: Step 3: Query verification
+ Infis->>Idp: Request JWT public key using OIDC Discovery
+ Idp-->>Infis: Return public key
+
+ Note over Infis: Step 4: JWT validation
+ Infis->>Client: Return short-lived access token
+
+ Note over Client,Infis: Step 5: Access Infisical API with Token
+ Client->>Infis: Make authenticated requests using the short-lived access token
+```
+
+## Concept
+
+At a high-level, Infisical authenticates a client by verifying the JWT and checking that it meets specific requirements (e.g. it is issued by a trusted identity provider) at the `/api/v1/auth/oidc-auth/login` endpoint. If successful,
+then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
+
+To be more specific:
+
+1. The GitLab pipeline requests an identity token from GitLab's identity provider.
+2. The fetched identity token is sent to Infisical at the `/api/v1/auth/oidc-auth/login` endpoint.
+3. Infisical fetches the public key that was used to sign the identity token from GitLab's identity provider using OIDC Discovery.
+4. Infisical validates the JWT using the public key provided by the identity provider and checks that the subject, audience, and claims of the token matches with the set criteria.
+5. If all is well, Infisical returns a short-lived access token that the GitLab pipeline can use to make authenticated requests to the Infisical API.
+
+
+ Infisical needs network-level access to GitLab's identity provider endpoints.
+
+
+## Guide
+
+In the following steps, we explore how to create and use identities to access the Infisical API using the OIDC Auth authentication method.
+
+
+
+ To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
+
+ 
+
+ When creating an identity, you specify an organization level [role](/documentation/platform/role-based-access-controls) for it to assume; you can configure roles in Organization Settings > Access Control > Organization Roles.
+
+ 
+
+ Now input a few details for your new identity. Here's some guidance for each field:
+
+ - Name (required): A friendly name for the identity.
+ - Role (required): A role from the **Organization Roles** tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
+
+ Once you've created an identity, you'll be redirected to a page where you can manage the identity.
+
+ 
+
+ Since the identity has been configured with Universal Auth by default, you should re-configure it to use OIDC Auth instead. To do this, press to edit the **Authentication** section,
+ remove the existing Universal Auth configuration, and add a new OIDC Auth configuration onto the identity.
+
+ 
+
+ 
+
+ Restrict access by configuring the Subject, Audiences, and Claims fields
+
+ Here's some more guidance on each field:
+ - OIDC Discovery URL: The URL used to retrieve the OpenID Connect configuration from the identity provider. This will be used to fetch the public key needed for verifying the provided JWT. For GitLab SaaS (GitLab.com), this should be set to `https://gitlab.com`. For self-hosted GitLab instances, use the domain of your GitLab instance.
+ - Issuer: The unique identifier of the identity provider issuing the JWT. This value is used to verify the iss (issuer) claim in the JWT to ensure the token is issued by a trusted provider. This should also be set to the domain of the Gitlab instance.
+ - CA Certificate: The PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints. For GitLab.com, this can be left blank.
+ - Subject: The expected principal that is the subject of the JWT. For GitLab pipelines, this should be set to a string that uniquely identifies the pipeline and its context, in the format `project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}` (e.g., `project_path:example-group/example-project:ref_type:branch:ref:main`).
+ - Claims: Additional information or attributes that should be present in the JWT for it to be valid. You can refer to GitLab's [documentation](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html#token-payload) for the list of supported claims.
+ - Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an acccess token in seconds. This value will be referenced at renewal time.
+ - Access Token Max TTL (default is `2592000` equivalent to 30 days): The maximum lifetime for an acccess token in seconds. This value will be referenced at renewal time.
+ - Access Token Max Number of Uses (default is `0`): The maximum number of times that an access token can be used; a value of `0` implies infinite number of uses.
+ - Access Token Trusted IPs: The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the `0.0.0.0/0`, allowing usage from any network address.
+ For more details on the appropriate values for the OIDC fields, refer to GitLab's [documentation](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html#token-payload).
+ The `subject`, `audiences`, and `claims` fields support glob pattern matching; however, we highly recommend using hardcoded values whenever possible.
+
+
+ To enable the identity to access project-level resources such as secrets within a specific project, you should add it to that project.
+
+ To do this, head over to the project you want to add the identity to and go to Project Settings > Access Control > Machine Identities and press **Add identity**.
+
+ Next, select the identity you want to add to the project and the project level role you want to allow it to assume. The project role assigned will determine what project level resources this identity can have access to.
+
+ 
+
+ 
+
+
+
+ As demonstration, we will be using the Infisical CLI to fetch Infisical secrets and utilize them within a GitLab pipeline.
+
+ To access Infisical secrets as the identity, you need to use an identity token from GitLab which matches the OIDC configuration defined for the machine identity.
+ This can be done by defining the `id_tokens` property. The resulting token would then be used to login with OIDC like the following: `infisical login --method=oidc-auth --oidc-jwt=$GITLAB_TOKEN`
+
+ Below is a complete example of how a GitLab pipeline can be configured to work with secrets from Infisical using the Infisical CLI with OIDC Auth:
+
+ ```yaml
+ image: ubuntu
+
+ stages:
+ - build
+
+ build-job:
+ stage: build
+ id_tokens:
+ INFISICAL_ID_TOKEN:
+ aud: infisical-aud-test
+ script:
+ - apt update && apt install -y curl
+ - curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash
+ - apt-get update && apt-get install -y infisical
+ - export INFISICAL_TOKEN=$(infisical login --method=oidc-auth --machine-identity-id=4e807a78-1b1c-4bd6-9609-ef2b0cf4fd54 --oidc-jwt=$INFISICAL_ID_TOKEN --silent --plain)
+ - infisical run --projectId=1d0443c1-cd43-4b3a-91a3-9d5f81254a89 --env=dev -- npm run build
+ ```
+
+ The `id_tokens` keyword is used to request an ID token for the job. In this example, an ID token named `INFISICAL_ID_TOKEN` is requested with the audience (`aud`) claim set to "infisical-aud-test". This ID token will be used to authenticate with Infisical.
+
+ Each identity access token has a time-to-live (TTL) which you can infer from the response of the login operation; the default TTL is `7200` seconds, which can be adjusted.
+
+ If an identity access token expires, it can no longer authenticate with the Infisical API. In this case, a new access token should be obtained by performing another login operation.
+
+
+
+
+
diff --git a/docs/mint.json b/docs/mint.json
index fae4ade1a..ef67e44cb 100644
--- a/docs/mint.json
+++ b/docs/mint.json
@@ -226,7 +226,8 @@
"pages": [
"documentation/platform/identities/oidc-auth/general",
"documentation/platform/identities/oidc-auth/github",
- "documentation/platform/identities/oidc-auth/circleci"
+ "documentation/platform/identities/oidc-auth/circleci",
+ "documentation/platform/identities/oidc-auth/gitlab"
]
},
"documentation/platform/mfa",
From 4053078d954e9a3d03e63beca544ecbd541b094a Mon Sep 17 00:00:00 2001
From: Sheen Capadngan
Date: Fri, 15 Nov 2024 01:36:33 +0800
Subject: [PATCH 26/36] misc: updated login self-hosting label for dedicated
---
cli/packages/cmd/login.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go
index 03974ba19..5e206a05a 100644
--- a/cli/packages/cmd/login.go
+++ b/cli/packages/cmd/login.go
@@ -532,7 +532,7 @@ func askForDomain() error {
const (
INFISICAL_CLOUD_US = "Infisical Cloud (US Region)"
INFISICAL_CLOUD_EU = "Infisical Cloud (EU Region)"
- SELF_HOSTING = "Self-Hosting"
+ SELF_HOSTING = "Self-Hosting or Dedicated Instance"
ADD_NEW_DOMAIN = "Add a new domain"
)
From bf97294dad4c43a408ca4ffcc3396e168e08838f Mon Sep 17 00:00:00 2001
From: Sheen Capadngan
Date: Fri, 15 Nov 2024 01:41:20 +0800
Subject: [PATCH 27/36] misc: added idp label
---
docs/documentation/platform/identities/oidc-auth/gitlab.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/documentation/platform/identities/oidc-auth/gitlab.mdx b/docs/documentation/platform/identities/oidc-auth/gitlab.mdx
index d6b2c8461..228392aa6 100644
--- a/docs/documentation/platform/identities/oidc-auth/gitlab.mdx
+++ b/docs/documentation/platform/identities/oidc-auth/gitlab.mdx
@@ -12,7 +12,7 @@ The following sequence diagram illustrates the OIDC Auth workflow for authentica
```mermaid
sequenceDiagram
participant Client as GitLab Pipeline
- participant Idp as Identity Provider
+ participant Idp as GitLab Identity Provider
participant Infis as Infisical
Client->>Idp: Step 1: Request identity token
From c79f84c06419fd280cab2f9128cd899d22bc723e Mon Sep 17 00:00:00 2001
From: Scott Wilson
Date: Thu, 14 Nov 2024 11:36:07 -0800
Subject: [PATCH 28/36] fix: use proxy on metadata permissions check to handle
missing keys
---
.../ee/services/permission/permission-fns.ts | 16 ++++++++++-
.../services/permission/permission-service.ts | 27 +++++++++++--------
2 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts
index 1ccee129f..80a58db0a 100644
--- a/backend/src/ee/services/permission/permission-fns.ts
+++ b/backend/src/ee/services/permission/permission-fns.ts
@@ -29,4 +29,18 @@ function validateOrgSSO(actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrg
}
}
-export { isAuthMethodSaml, validateOrgSSO };
+const escapeHandlebarsMissingMetadata = (obj: Record) => {
+ const handler = {
+ get(target: Record, prop: string) {
+ if (!(prop in target)) {
+ // eslint-disable-next-line no-param-reassign
+ target[prop] = `{{identity.metadata.${prop}}}`; // Add missing key as an "own" property
+ }
+ return target[prop];
+ }
+ };
+
+ return new Proxy(obj, handler);
+};
+
+export { escapeHandlebarsMissingMetadata, isAuthMethodSaml, validateOrgSSO };
diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts
index e762d00ec..13645b8f1 100644
--- a/backend/src/ee/services/permission/permission-service.ts
+++ b/backend/src/ee/services/permission/permission-service.ts
@@ -21,7 +21,7 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
import { TPermissionDALFactory } from "./permission-dal";
-import { validateOrgSSO } from "./permission-fns";
+import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns";
import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO } from "./permission-service-types";
import {
buildServiceTokenProjectPermission,
@@ -227,11 +227,13 @@ export const permissionServiceFactory = ({
})) || [];
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
- const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false, strict: true });
- const metadataKeyValuePair = objectify(
- userProjectPermission.metadata,
- (i) => i.key,
- (i) => i.value
+ const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
+ const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
+ objectify(
+ userProjectPermission.metadata,
+ (i) => i.key,
+ (i) => i.value
+ )
);
const interpolateRules = templatedRules(
{
@@ -292,12 +294,15 @@ export const permissionServiceFactory = ({
})) || [];
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
- const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false, strict: true });
- const metadataKeyValuePair = objectify(
- identityProjectPermission.metadata,
- (i) => i.key,
- (i) => i.value
+ const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
+ const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
+ objectify(
+ identityProjectPermission.metadata,
+ (i) => i.key,
+ (i) => i.value
+ )
);
+
const interpolateRules = templatedRules(
{
identity: {
From 1295b68d80983b5c9f3e65bc491c51c3f32a7f3b Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Thu, 14 Nov 2024 23:07:30 -0700
Subject: [PATCH 29/36] Fix ca version migration We didn't do a check to see if
the column already exists. Because of this, we get this error during
migrations:
```
| migration file "20240802181855_ca-cert-version.ts" failed
infisical-db-migration | migration failed with error: alter table "certificates" add column "caCertId" uuid null - column "caCertId" of relation "certificates" already exists
```
---
.../20240802181855_ca-cert-version.ts | 22 ++--
docker-compose.prod.yml | 118 +++++++++---------
2 files changed, 71 insertions(+), 69 deletions(-)
diff --git a/backend/src/db/migrations/20240802181855_ca-cert-version.ts b/backend/src/db/migrations/20240802181855_ca-cert-version.ts
index 24eca185d..c38a2d42a 100644
--- a/backend/src/db/migrations/20240802181855_ca-cert-version.ts
+++ b/backend/src/db/migrations/20240802181855_ca-cert-version.ts
@@ -64,23 +64,25 @@ export async function up(knex: Knex): Promise {
}
if (await knex.schema.hasTable(TableName.Certificate)) {
- await knex.schema.alterTable(TableName.Certificate, (t) => {
- t.uuid("caCertId").nullable();
- t.foreign("caCertId").references("id").inTable(TableName.CertificateAuthorityCert);
- });
+ const hasCaCertIdColumn = await knex.schema.hasColumn(TableName.Certificate, "caCertId");
+ if (!hasCaCertIdColumn) {
+ await knex.schema.alterTable(TableName.Certificate, (t) => {
+ t.uuid("caCertId").nullable();
+ t.foreign("caCertId").references("id").inTable(TableName.CertificateAuthorityCert);
+ });
- await knex.raw(`
+ await knex.raw(`
UPDATE "${TableName.Certificate}" cert
SET "caCertId" = (
SELECT caCert.id
FROM "${TableName.CertificateAuthorityCert}" caCert
WHERE caCert."caId" = cert."caId"
- )
- `);
+ )`);
- await knex.schema.alterTable(TableName.Certificate, (t) => {
- t.uuid("caCertId").notNullable().alter();
- });
+ await knex.schema.alterTable(TableName.Certificate, (t) => {
+ t.uuid("caCertId").notNullable().alter();
+ });
+ }
}
}
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
index 40c17a7fe..a2d4d6bf3 100644
--- a/docker-compose.prod.yml
+++ b/docker-compose.prod.yml
@@ -3,70 +3,70 @@ version: "3"
services:
db-migration:
container_name: infisical-db-migration
- depends_on:
- db:
- condition: service_healthy
- image: infisical/infisical:latest-postgres
+ # depends_on:
+ # db:
+ # condition: service_healthy
+ image: infisical/infisical:v0.94.0-postgres
env_file: .env
command: npm run migration:latest
pull_policy: always
- networks:
- - infisical
+ # networks:
+ # - infisical
- backend:
- container_name: infisical-backend
- restart: unless-stopped
- depends_on:
- db:
- condition: service_healthy
- redis:
- condition: service_started
- db-migration:
- condition: service_completed_successfully
- image: infisical/infisical:latest-postgres
- pull_policy: always
- env_file: .env
- ports:
- - 80:8080
- environment:
- - NODE_ENV=production
- networks:
- - infisical
+# backend:
+# container_name: infisical-backend
+# restart: unless-stopped
+# depends_on:
+# db:
+# condition: service_healthy
+# redis:
+# condition: service_started
+# db-migration:
+# condition: service_completed_successfully
+# image: infisical/infisical:latest-postgres
+# pull_policy: always
+# env_file: .env
+# ports:
+# - 80:8080
+# environment:
+# - NODE_ENV=production
+# networks:
+# - infisical
- redis:
- image: redis
- container_name: infisical-dev-redis
- env_file: .env
- restart: always
- environment:
- - ALLOW_EMPTY_PASSWORD=yes
- ports:
- - 6379:6379
- networks:
- - infisical
- volumes:
- - redis_data:/data
+# redis:
+# image: redis
+# container_name: infisical-dev-redis
+# env_file: .env
+# restart: always
+# environment:
+# - ALLOW_EMPTY_PASSWORD=yes
+# ports:
+# - 6379:6379
+# networks:
+# - infisical
+# volumes:
+# - redis_data:/data
- db:
- container_name: infisical-db
- image: postgres:14-alpine
- restart: always
- env_file: .env
- volumes:
- - pg_data:/var/lib/postgresql/data
- networks:
- - infisical
- healthcheck:
- test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
- interval: 5s
- timeout: 10s
- retries: 10
+# db:
+# container_name: infisical-db
+# image: postgres:14-alpine
+# restart: always
+# env_file: .env
+# volumes:
+# - pg_data:/var/lib/postgresql/data
+# networks:
+# - infisical
+# healthcheck:
+# test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
+# interval: 5s
+# timeout: 10s
+# retries: 10
-volumes:
- pg_data:
- driver: local
- redis_data:
- driver: local
+# volumes:
+# pg_data:
+# driver: local
+# redis_data:
+# driver: local
-networks:
- infisical:
+# networks:
+# infisical:
From d6e1ed4d1ebd50d18a039b8c6434b3010f05342a Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Thu, 14 Nov 2024 23:10:54 -0700
Subject: [PATCH 30/36] revert docker compose changes
---
docker-compose.prod.yml | 118 ++++++++++++++++++++--------------------
1 file changed, 59 insertions(+), 59 deletions(-)
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
index a2d4d6bf3..77a1e04ab 100644
--- a/docker-compose.prod.yml
+++ b/docker-compose.prod.yml
@@ -3,70 +3,70 @@ version: "3"
services:
db-migration:
container_name: infisical-db-migration
- # depends_on:
- # db:
- # condition: service_healthy
- image: infisical/infisical:v0.94.0-postgres
+ depends_on:
+ db:
+ condition: service_healthy
+ image: infisical/infisical:latest-postgres
env_file: .env
command: npm run migration:latest
pull_policy: always
- # networks:
- # - infisical
+ networks:
+ - infisical
-# backend:
-# container_name: infisical-backend
-# restart: unless-stopped
-# depends_on:
-# db:
-# condition: service_healthy
-# redis:
-# condition: service_started
-# db-migration:
-# condition: service_completed_successfully
-# image: infisical/infisical:latest-postgres
-# pull_policy: always
-# env_file: .env
-# ports:
-# - 80:8080
-# environment:
-# - NODE_ENV=production
-# networks:
-# - infisical
+ backend:
+ container_name: infisical-backend
+ restart: unless-stopped
+ depends_on:
+ db:
+ condition: service_healthy
+ redis:
+ condition: service_started
+ db-migration:
+ condition: service_completed_successfully
+ image: infisical/infisical:latest-postgres
+ pull_policy: always
+ env_file: .env
+ ports:
+ - 80:8080
+ environment:
+ - NODE_ENV=production
+ networks:
+ - infisical
-# redis:
-# image: redis
-# container_name: infisical-dev-redis
-# env_file: .env
-# restart: always
-# environment:
-# - ALLOW_EMPTY_PASSWORD=yes
-# ports:
-# - 6379:6379
-# networks:
-# - infisical
-# volumes:
-# - redis_data:/data
+ redis:
+ image: redis
+ container_name: infisical-dev-redis
+ env_file: .env
+ restart: always
+ environment:
+ - ALLOW_EMPTY_PASSWORD=yes
+ ports:
+ - 6379:6379
+ networks:
+ - infisical
+ volumes:
+ - redis_data:/data
-# db:
-# container_name: infisical-db
-# image: postgres:14-alpine
-# restart: always
-# env_file: .env
-# volumes:
-# - pg_data:/var/lib/postgresql/data
-# networks:
-# - infisical
-# healthcheck:
-# test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
-# interval: 5s
-# timeout: 10s
-# retries: 10
+ db:
+ container_name: infisical-db
+ image: postgres:14-alpine
+ restart: always
+ env_file: .env
+ volumes:
+ - pg_data:/var/lib/postgresql/data
+ networks:
+ - infisical
+ healthcheck:
+ test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
+ interval: 5s
+ timeout: 10s
+ retries: 10
-# volumes:
-# pg_data:
-# driver: local
-# redis_data:
-# driver: local
+volumes:
+ pg_data:
+ driver: local
+ redis_data:
+ driver: local
-# networks:
-# infisical:
+networks:
+ infisical:
\ No newline at end of file
From 8819abd7105facbdec7c666fe88684ca08f8f391 Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Fri, 15 Nov 2024 00:42:30 -0700
Subject: [PATCH 31/36] only create triggers when create new table
---
.../db/migrations/20240818024923_cert-alerting.ts | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/backend/src/db/migrations/20240818024923_cert-alerting.ts b/backend/src/db/migrations/20240818024923_cert-alerting.ts
index f60ce8c02..50848ce15 100644
--- a/backend/src/db/migrations/20240818024923_cert-alerting.ts
+++ b/backend/src/db/migrations/20240818024923_cert-alerting.ts
@@ -13,9 +13,9 @@ export async function up(knex: Knex): Promise {
t.string("name").notNullable();
t.string("description").notNullable();
});
- }
- await createOnUpdateTrigger(knex, TableName.PkiCollection);
+ await createOnUpdateTrigger(knex, TableName.PkiCollection);
+ }
if (!(await knex.schema.hasTable(TableName.PkiCollectionItem))) {
await knex.schema.createTable(TableName.PkiCollectionItem, (t) => {
@@ -28,9 +28,9 @@ export async function up(knex: Knex): Promise {
t.uuid("certId").nullable();
t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE");
});
- }
- await createOnUpdateTrigger(knex, TableName.PkiCollectionItem);
+ await createOnUpdateTrigger(knex, TableName.PkiCollectionItem);
+ }
if (!(await knex.schema.hasTable(TableName.PkiAlert))) {
await knex.schema.createTable(TableName.PkiAlert, (t) => {
@@ -45,9 +45,9 @@ export async function up(knex: Knex): Promise {
t.string("recipientEmails").notNullable();
t.unique(["name", "projectId"]);
});
- }
- await createOnUpdateTrigger(knex, TableName.PkiAlert);
+ await createOnUpdateTrigger(knex, TableName.PkiAlert);
+ }
}
export async function down(knex: Knex): Promise {
From d75e49dce5aca19951873632f96b1c4b474d0b6c Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Fri, 15 Nov 2024 00:52:08 -0700
Subject: [PATCH 32/36] update trigegr to only create if it doesn't exit
---
.../20240818024923_cert-alerting.ts | 12 ++++----
backend/src/db/utils.ts | 30 ++++++++++++++-----
2 files changed, 29 insertions(+), 13 deletions(-)
diff --git a/backend/src/db/migrations/20240818024923_cert-alerting.ts b/backend/src/db/migrations/20240818024923_cert-alerting.ts
index 50848ce15..f60ce8c02 100644
--- a/backend/src/db/migrations/20240818024923_cert-alerting.ts
+++ b/backend/src/db/migrations/20240818024923_cert-alerting.ts
@@ -13,10 +13,10 @@ export async function up(knex: Knex): Promise {
t.string("name").notNullable();
t.string("description").notNullable();
});
-
- await createOnUpdateTrigger(knex, TableName.PkiCollection);
}
+ await createOnUpdateTrigger(knex, TableName.PkiCollection);
+
if (!(await knex.schema.hasTable(TableName.PkiCollectionItem))) {
await knex.schema.createTable(TableName.PkiCollectionItem, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
@@ -28,10 +28,10 @@ export async function up(knex: Knex): Promise {
t.uuid("certId").nullable();
t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE");
});
-
- await createOnUpdateTrigger(knex, TableName.PkiCollectionItem);
}
+ await createOnUpdateTrigger(knex, TableName.PkiCollectionItem);
+
if (!(await knex.schema.hasTable(TableName.PkiAlert))) {
await knex.schema.createTable(TableName.PkiAlert, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
@@ -45,9 +45,9 @@ export async function up(knex: Knex): Promise {
t.string("recipientEmails").notNullable();
t.unique(["name", "projectId"]);
});
-
- await createOnUpdateTrigger(knex, TableName.PkiAlert);
}
+
+ await createOnUpdateTrigger(knex, TableName.PkiAlert);
}
export async function down(knex: Knex): Promise {
diff --git a/backend/src/db/utils.ts b/backend/src/db/utils.ts
index 68c400596..e06cdd3f1 100644
--- a/backend/src/db/utils.ts
+++ b/backend/src/db/utils.ts
@@ -2,6 +2,9 @@ import { Knex } from "knex";
import { TableName } from "./schemas";
+interface PgTriggerResult {
+ rows: Array<{ exists: boolean }>;
+}
export const createJunctionTable = (knex: Knex, tableName: TableName, table1Name: TableName, table2Name: TableName) =>
knex.schema.createTable(tableName, (table) => {
table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
@@ -28,13 +31,26 @@ DROP FUNCTION IF EXISTS on_update_timestamp() CASCADE;
// we would be using this to apply updatedAt where ever we wanta
// remember to set `timestamps(true,true,true)` before this on schema
-export const createOnUpdateTrigger = (knex: Knex, tableName: string) =>
- knex.raw(`
-CREATE TRIGGER "${tableName}_updatedAt"
-BEFORE UPDATE ON ${tableName}
-FOR EACH ROW
-EXECUTE PROCEDURE on_update_timestamp();
-`);
+export const createOnUpdateTrigger = async (knex: Knex, tableName: string) => {
+ const triggerExists = await knex.raw(`
+ SELECT EXISTS (
+ SELECT 1
+ FROM pg_trigger
+ WHERE tgname = '${tableName}_updatedAt'
+ );
+ `);
+
+ if (!triggerExists?.rows?.[0]?.exists) {
+ return knex.raw(`
+ CREATE TRIGGER "${tableName}_updatedAt"
+ BEFORE UPDATE ON ${tableName}
+ FOR EACH ROW
+ EXECUTE PROCEDURE on_update_timestamp();
+ `);
+ }
+
+ return null;
+};
export const dropOnUpdateTrigger = (knex: Knex, tableName: string) =>
knex.raw(`DROP TRIGGER IF EXISTS "${tableName}_updatedAt" ON ${tableName}`);
From 682b552fdcd8a4a2b798786fdc5895db6f9f35b1 Mon Sep 17 00:00:00 2001
From: Sheen Capadngan
Date: Sat, 16 Nov 2024 03:15:39 +0800
Subject: [PATCH 33/36] misc: addressed remaining comments
---
backend/src/services/totp/totp-fns.ts | 3 +++
backend/src/services/totp/totp-service.ts | 17 ++++++++---------
.../src/pages/login/select-organization.tsx | 10 ++++++----
frontend/src/views/Login/Mfa.tsx | 4 ++--
4 files changed, 19 insertions(+), 15 deletions(-)
create mode 100644 backend/src/services/totp/totp-fns.ts
diff --git a/backend/src/services/totp/totp-fns.ts b/backend/src/services/totp/totp-fns.ts
new file mode 100644
index 000000000..9e9aae52c
--- /dev/null
+++ b/backend/src/services/totp/totp-fns.ts
@@ -0,0 +1,3 @@
+import crypto from "node:crypto";
+
+export const generateRecoveryCode = () => String(crypto.randomInt(10 ** 7, 10 ** 8 - 1));
diff --git a/backend/src/services/totp/totp-service.ts b/backend/src/services/totp/totp-service.ts
index f304f01b8..591a66ed6 100644
--- a/backend/src/services/totp/totp-service.ts
+++ b/backend/src/services/totp/totp-service.ts
@@ -1,5 +1,3 @@
-import crypto from "node:crypto";
-
import { authenticator } from "otplib";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -7,6 +5,7 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/
import { TKmsServiceFactory } from "../kms/kms-service";
import { TUserDALFactory } from "../user/user-dal";
import { TTotpConfigDALFactory } from "./totp-config-dal";
+import { generateRecoveryCode } from "./totp-fns";
import {
TCreateUserTotpRecoveryCodesDTO,
TDeleteUserTotpConfigDTO,
@@ -25,6 +24,8 @@ type TTotpServiceFactoryDep = {
export type TTotpServiceFactory = ReturnType;
+const MAX_RECOVERY_CODE_LIMIT = 10;
+
export const totpServiceFactory = ({ totpConfigDAL, kmsService, userDAL }: TTotpServiceFactoryDep) => {
const getUserTotpConfig = async ({ userId }: TGetUserTotpConfigDTO) => {
const totpConfig = await totpConfigDAL.findOne({
@@ -82,7 +83,7 @@ export const totpServiceFactory = ({ totpConfigDAL, kmsService, userDAL }: TTotp
// create new TOTP configuration
const secret = authenticator.generateSecret();
const encryptedSecret = encryptWithRoot(Buffer.from(secret));
- const recoveryCodes = Array.from({ length: 10 }).map(() => String(crypto.randomInt(10 ** 7, 10 ** 8 - 1)));
+ const recoveryCodes = Array.from({ length: MAX_RECOVERY_CODE_LIMIT }).map(generateRecoveryCode);
const encryptedRecoveryCodes = encryptWithRoot(Buffer.from(recoveryCodes.join(",")));
const newTotpConfig = await totpConfigDAL.create({
userId,
@@ -241,16 +242,14 @@ export const totpServiceFactory = ({ totpConfigDAL, kmsService, userDAL }: TTotp
}
const recoveryCodes = decryptWithRoot(totpConfig.encryptedRecoveryCodes).toString().split(",");
- if (recoveryCodes.length >= 10) {
+ if (recoveryCodes.length >= MAX_RECOVERY_CODE_LIMIT) {
throw new BadRequestError({
- message: "Cannot have more than 10 recovery codes at a time"
+ message: `Cannot have more than ${MAX_RECOVERY_CODE_LIMIT} recovery codes at a time`
});
}
- const toGenerateCount = 10 - recoveryCodes.length;
- const newRecoveryCodes = Array.from({ length: toGenerateCount }).map(() =>
- String(crypto.randomInt(10 ** 7, 10 ** 8 - 1))
- );
+ const toGenerateCount = MAX_RECOVERY_CODE_LIMIT - recoveryCodes.length;
+ const newRecoveryCodes = Array.from({ length: toGenerateCount }).map(generateRecoveryCode);
const encryptedRecoveryCodes = encryptWithRoot(Buffer.from([...recoveryCodes, ...newRecoveryCodes].join(",")));
await totpConfigDAL.updateById(totpConfig.id, {
diff --git a/frontend/src/pages/login/select-organization.tsx b/frontend/src/pages/login/select-organization.tsx
index c3d939e7b..e2ebe37ae 100644
--- a/frontend/src/pages/login/select-organization.tsx
+++ b/frontend/src/pages/login/select-organization.tsx
@@ -91,10 +91,12 @@ export default function LoginPage() {
return;
}
- const { token, isMfaEnabled, mfaMethod } = await selectOrg.mutateAsync({
- organizationId: organization.id,
- userAgent: callbackPort ? UserAgentType.CLI : undefined
- });
+ const { token, isMfaEnabled, mfaMethod } = await selectOrg
+ .mutateAsync({
+ organizationId: organization.id,
+ userAgent: callbackPort ? UserAgentType.CLI : undefined
+ })
+ .finally(() => setIsInitialOrgCheckLoading(false));
if (isMfaEnabled) {
SecurityClient.setMfaToken(token);
diff --git a/frontend/src/views/Login/Mfa.tsx b/frontend/src/views/Login/Mfa.tsx
index 6132d05bf..d49455190 100644
--- a/frontend/src/views/Login/Mfa.tsx
+++ b/frontend/src/views/Login/Mfa.tsx
@@ -114,7 +114,7 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
return (
<>
- Your organization requires mobile authenticator to be configured.
+ Your organization requires mobile authentication to be configured.
- No access to both codes? Reset your account
+ Lost your recovery codes? Reset your account
From 65f122bd41eb452bbddcdcc681a40cb512a3974b Mon Sep 17 00:00:00 2001
From: Daniel Hougaard
Date: Sat, 16 Nov 2024 01:37:43 +0400
Subject: [PATCH 34/36] Update index.cjs
---
npm/src/index.cjs | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/npm/src/index.cjs b/npm/src/index.cjs
index 398b3501a..f1ff51069 100644
--- a/npm/src/index.cjs
+++ b/npm/src/index.cjs
@@ -77,7 +77,15 @@ async function extractZip(buffer, targetPath) {
zipfile.openReadStream(entry, (err, readStream) => {
if (err) return reject(err);
- const outputPath = path.join(targetPath, entry.fileName.includes("infisical") ? "infisical" : entry.fileName);
+ let fileName = entry.fileName;
+
+ if (entry.fileName.endsWith(".exe")) {
+ fileName = "infisical.exe";
+ } else if (entry.fileName.includes("infisical")) {
+ fileName = "infisical";
+ }
+
+ const outputPath = path.join(targetPath, fileName);
const writeStream = fs.createWriteStream(outputPath);
readStream.pipe(writeStream);
@@ -140,8 +148,14 @@ async function main() {
});
}
- // Give the binary execute permissions if we're not on Windows
- if (PLATFORM !== "win32") {
+ // Platform-specific tasks
+ if (PLATFORM === "windows") {
+ // We create an empty file called 'infisical'. This file has no functionality, except allowing NPM to correctly create the symlink.
+ // Reason why this doesn't work without the empty file, is because the files downloaded are a .ps1, .exe, and .cmd file. None of these match the binary name from the package.json['bin'] field.
+ // This is a bit hacky, but it assures that the symlink is correctly created.
+ fs.closeSync(fs.openSync(path.join(outputDir, "infisical"), "w"));
+ } else {
+ // Unix systems only need chmod
fs.chmodSync(path.join(outputDir, "infisical"), "755");
}
} catch (error) {
From 5c9ec1e4beb853ca92c424bb6d3f52e044f47f59 Mon Sep 17 00:00:00 2001
From: Vlad Matsiiako <78047717+vmatsiiako@users.noreply.github.com>
Date: Sun, 17 Nov 2024 09:55:32 -0500
Subject: [PATCH 35/36] Fix typo in docs
---
docs/self-hosting/deployment-options/kubernetes-helm.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/self-hosting/deployment-options/kubernetes-helm.mdx b/docs/self-hosting/deployment-options/kubernetes-helm.mdx
index ac8a098de..35b003c2d 100644
--- a/docs/self-hosting/deployment-options/kubernetes-helm.mdx
+++ b/docs/self-hosting/deployment-options/kubernetes-helm.mdx
@@ -79,7 +79,7 @@ description: "Learn how to use Helm chart to install Infisical on your Kubernete
- Infisical relies a relational database, which means that database schemas need to be migrated before the instance can become operational.
+ Infisical relies on a relational database, which means that database schemas need to be migrated before the instance can become operational.
To automate this process, the chart includes a option named `infisical.autoDatabaseSchemaMigration`.
When this option is enabled, a deployment/upgrade will only occur _after_ a successful schema migration.
From 6c49c7da3cb1866d7d0f2c01a2b781e6052f4711 Mon Sep 17 00:00:00 2001
From: Vladyslav Matsiiako
Date: Sun, 17 Nov 2024 23:43:57 -0500
Subject: [PATCH 36/36] added handbook updates
---
company/handbook/compensation.mdx | 28 ++++++++++++++++++++++++++++
company/mint.json | 1 +
2 files changed, 29 insertions(+)
create mode 100644 company/handbook/compensation.mdx
diff --git a/company/handbook/compensation.mdx b/company/handbook/compensation.mdx
new file mode 100644
index 000000000..4131c7ee6
--- /dev/null
+++ b/company/handbook/compensation.mdx
@@ -0,0 +1,28 @@
+---
+title: "Compensation"
+sidebarTitle: "Compensation"
+description: "This guide explains how various compensation processes work at Infisical."
+---
+
+## Probation period
+
+We are fully committed to ensuring that you are set up for success, but also understand that it may take some time to determine whether or not there is a long term fit between you and Infisical.
+
+The first 3 months of your employment with Infisical is a probation period. During this time, you can choose to end your contract with 1 week's notice. If we chose to end your contract, Infisical will pay you 4 weeks' pay, but usually ask you to finish on the same day.
+
+People in sales roles, such as Account Executives, have a 6 month probation period - this is to account for the fact that it can be difficult to establish whether or not someone is able to close contracts within their first 3 months, given sales cycles.
+
+Your manager is responsible for monitoring and specifically reviewing your performance throughout this initial period. If under-performance is a concern, or if there is any hesitation regarding the future at Infisical, this should be discussed immediately with you and your manager.
+
+
+## Severance
+
+At Infisical, average performance gets a generous severance.
+
+If Infisical decides to end your contract after the first 3 months of employment have been completed, we will give you 10 weeks' pay. It is likely we will ask you to stop working immediately.
+
+If the decision to leave is yours, then we just require 1 month of notice.
+
+We have structured notice in this way as we believe it is in neither Infisical's nor your interest to lock you into a role that is no longer right for you due to financial considerations. This extended notice period only applies in the case of under-performance or a change in business needs - if your contract is terminated due to gross misconduct then you may be dismissed without notice. If this policy conflicts with the requirements of your local jurisdiction, then those local laws will take priority.
+
+
diff --git a/company/mint.json b/company/mint.json
index e6ef851cc..bc2b87ec5 100644
--- a/company/mint.json
+++ b/company/mint.json
@@ -58,6 +58,7 @@
"pages": [
"handbook/onboarding",
"handbook/spending-money",
+ "handbook/compensation",
"handbook/time-off",
"handbook/hiring"
]