Compare commits

..

33 Commits

Author SHA1 Message Date
Maidul Islam
f7e658e62b rename bit bucket options 2024-06-25 16:31:56 -04:00
Sheen Capadngan
6029eaa9df misc: modified step title 2024-06-24 20:17:31 +08:00
Sheen Capadngan
8703314c0c doc: added bitbucket integration with cli 2024-06-24 20:11:53 +08:00
Vlad Matsiiako
6d9330e870 Update machine-identities.mdx 2024-06-23 14:27:11 -07:00
Vlad Matsiiako
d026a9b988 Update mint.json 2024-06-23 13:23:10 -07:00
Vlad Matsiiako
c2c693d295 Update mint.json 2024-06-23 13:22:30 -07:00
Vlad Matsiiako
c9c77f6c58 Update saml docs 2024-06-22 19:02:43 -07:00
Vlad Matsiiako
36a34b0f58 Update sdk docs 2024-06-22 18:38:37 -07:00
Vlad Matsiiako
45c153e592 Update terraform-cloud.mdx 2024-06-22 18:31:12 -07:00
Vlad Matsiiako
eeaabe44ec Update LDAP docs 2024-06-22 16:44:42 -07:00
Maidul Islam
4b37c0f1c4 Merge pull request #2004 from Infisical/daniel/show-imported-overwritten-values-overview
feat(platform): Show imported/overwritten values in secret overview
2024-06-21 17:13:02 -04:00
Maidul Islam
cbef9ea514 Merge pull request #2003 from Infisical/sharing-ui-update
updated secret sharing design
2024-06-21 10:03:10 -04:00
Daniel Hougaard
d0f8394f50 Fix: Added explicit SecretType type 2024-06-21 15:23:50 +02:00
Daniel Hougaard
9c06cab99d Fix: Added explicit SecretType type 2024-06-21 15:23:31 +02:00
Daniel Hougaard
c43a18904d Feat: Show overwritten value in secret overview and allow for edits 2024-06-21 15:23:09 +02:00
Daniel Hougaard
dc0fe6920c Feat: Show overwritten value in secret overview and allow for edits 2024-06-21 15:22:48 +02:00
Daniel Hougaard
077cbc97d5 Update encryptSecrets.ts 2024-06-21 15:21:52 +02:00
Daniel Hougaard
f3da676b88 Update checkOverrides.ts 2024-06-21 15:21:44 +02:00
Daniel Hougaard
988c612048 Update DropZone.tsx 2024-06-21 15:20:48 +02:00
Daniel Hougaard
7cf7eb5acb Fix: Added explicit SecretType type 2024-06-21 15:20:37 +02:00
Daniel Hougaard
a2fd071b62 Update SecretOverviewPage.tsx 2024-06-21 14:31:45 +02:00
Daniel Hougaard
0d7a07dea3 Update SecretEditRow.tsx 2024-06-21 14:31:41 +02:00
Daniel Hougaard
f676b44335 Feat: Show imported values in overview 2024-06-21 14:31:36 +02:00
Daniel Hougaard
00d83f9136 Update SecretInput.tsx 2024-06-21 14:31:29 +02:00
Daniel Hougaard
eca6871cbc Feat: Show imported values in overview 2024-06-21 14:31:22 +02:00
Maidul Islam
3767ec9521 Update build-staging-and-deploy-aws.yml 2024-06-21 00:21:07 -04:00
Maidul Islam
908358b841 Update build-staging-and-deploy-aws.yml 2024-06-20 19:32:30 -04:00
Maidul Islam
b2a88a4384 Update build-staging-and-deploy-aws.yml 2024-06-20 16:01:55 -04:00
Maidul Islam
ab73e77499 Update build-staging-and-deploy-aws.yml 2024-06-20 15:54:06 -04:00
Maidul Islam
095a049661 Update build-staging-and-deploy-aws.yml 2024-06-20 15:48:54 -04:00
Maidul Islam
3a51155d23 Merge pull request #2001 from akhilmhdh/fix/handover-enc-v1
feat: resolved generate srp failing for user enc v1 users
2024-06-20 14:05:52 -04:00
=
c5f361a3e5 feat: updated fail message over srp key generation as per review 2024-06-20 22:30:45 +05:30
=
5ace8ed073 feat: resolved generate srp failing for user enc v1 users 2024-06-20 22:12:44 +05:30
31 changed files with 281 additions and 133 deletions

View File

@@ -50,6 +50,13 @@ jobs:
environment:
name: Gamma
steps:
- uses: twingate/github-action@v1
with:
# The Twingate Service Key used to connect Twingate to the proper service
# Learn more about [Twingate Services](https://docs.twingate.com/docs/services)
#
# Required
service-key: ${{ secrets.TWINGATE_GAMMA_SERVICE_KEY }}
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node.js environment
@@ -74,21 +81,21 @@ jobs:
uses: pr-mpt/actions-commit-hash@v2
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition infisical-core-platform --query taskDefinition > task-definition.json
aws ecs describe-task-definition --task-definition infisical-core-gamma-stage --query taskDefinition > task-definition.json
- name: Render Amazon ECS task definition
id: render-web-container
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: infisical-core-platform
container-name: infisical-core
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-core-platform
cluster: infisical-core-platform
service: infisical-core-gamma-stage
cluster: infisical-gamma-stage
wait-for-service-stability: true
production-postgres-deployment:

View File

@@ -101,33 +101,51 @@ export const getUserPrivateKey = async (
password: string,
user: Pick<
TUserEncryptionKeys,
"protectedKeyTag" | "protectedKey" | "protectedKeyIV" | "encryptedPrivateKey" | "iv" | "salt" | "tag"
| "protectedKeyTag"
| "protectedKey"
| "protectedKeyIV"
| "encryptedPrivateKey"
| "iv"
| "salt"
| "tag"
| "encryptionVersion"
>
) => {
const derivedKey = await argon2.hash(password, {
salt: Buffer.from(user.salt),
memoryCost: 65536,
timeCost: 3,
parallelism: 1,
hashLength: 32,
type: argon2.argon2id,
raw: true
});
if (!derivedKey) throw new Error("Failed to derive key from password");
const key = decryptSymmetric128BitHexKeyUTF8({
ciphertext: user.protectedKey as string,
iv: user.protectedKeyIV as string,
tag: user.protectedKeyTag as string,
key: derivedKey
});
if (user.encryptionVersion === 1) {
return decryptSymmetric128BitHexKeyUTF8({
ciphertext: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
key: password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
});
}
if (user.encryptionVersion === 2 && user.protectedKey && user.protectedKeyIV && user.protectedKeyTag) {
const derivedKey = await argon2.hash(password, {
salt: Buffer.from(user.salt),
memoryCost: 65536,
timeCost: 3,
parallelism: 1,
hashLength: 32,
type: argon2.argon2id,
raw: true
});
if (!derivedKey) throw new Error("Failed to derive key from password");
const key = decryptSymmetric128BitHexKeyUTF8({
ciphertext: user.protectedKey,
iv: user.protectedKeyIV,
tag: user.protectedKeyTag,
key: derivedKey
});
const privateKey = decryptSymmetric128BitHexKeyUTF8({
ciphertext: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
key: Buffer.from(key, "hex")
});
return privateKey;
const privateKey = decryptSymmetric128BitHexKeyUTF8({
ciphertext: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
key: Buffer.from(key, "hex")
});
return privateKey;
}
throw new Error(`GetUserPrivateKey: Encryption version not found`);
};
export const buildUserProjectKey = async (privateKey: string, publickey: string) => {

View File

@@ -9,6 +9,7 @@ import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, DatabaseError, UnauthorizedError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TTokenDALFactory } from "../auth-token/auth-token-dal";
@@ -258,7 +259,13 @@ export const authLoginServiceFactory = ({
});
// from password decrypt the private key
if (password) {
const privateKey = await getUserPrivateKey(password, userEnc);
const privateKey = await getUserPrivateKey(password, userEnc).catch((err) => {
logger.error(
err,
`loginExchangeClientProof: private key generation failed for [userId=${user.id}] and [email=${user.email}] `
);
return "";
});
const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND);
const { iv, tag, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey);
await userDAL.updateUserEncryptionByUserId(userEnc.userId, {

View File

@@ -165,7 +165,8 @@ export const authSignupServiceFactory = ({
protectedKeyTag,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
tag: encryptedPrivateKeyTag,
encryptionVersion: 2
});
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey);
const updateduser = await authDAL.transaction(async (tx) => {
@@ -325,7 +326,8 @@ export const authSignupServiceFactory = ({
protectedKeyTag,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
tag: encryptedPrivateKeyTag,
encryptionVersion: 2
});
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey);
const updateduser = await authDAL.transaction(async (tx) => {

View File

@@ -98,6 +98,7 @@ export const superAdminServiceFactory = ({
if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
const privateKey = await getUserPrivateKey(password, {
encryptionVersion: 2,
salt,
protectedKey,
protectedKeyIV,

View File

@@ -26,13 +26,6 @@ A typical workflow for using identities consists of four steps:
3. Authenticating the identity with the Infisical API based on the configured authentication method on it and receiving a short-lived access token back.
4. Authenticating subsequent requests with the Infisical API using the short-lived access token.
<Note>
Currently, identities can only be used to make authenticated requests to the Infisical API, SDKs, Terraform, Kubernetes Operator, and Infisical Agent. They do not work with clients such as CLI, Ansible look up plugin, etc.
Machine Identity support for the rest of the clients is planned to be released in the current quarter.
</Note>
## Authentication Methods
To interact with various resources in Infisical, Machine Identities are able to authenticate using:

View File

@@ -14,8 +14,6 @@ then you should contact sales@infisical.com to purchase an enterprise license to
You can configure your organization in Infisical to have members authenticate with the platform via [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol).
To note, configuring LDAP retains the end-to-end encrypted nature of authentication in Infisical because we decouple the authentication and decryption steps; the LDAP server cannot and will not have access to the decryption key needed to decrypt your secrets.
LDAP providers:
- Active Directory

View File

@@ -15,9 +15,6 @@ description: "Learn how to log in to Infisical via SSO protocols."
You can configure your organization in Infisical to have members authenticate with the platform via protocols like [SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0).
To note, Infisical's SSO implementation decouples the **authentication** and **decryption** steps  which implies that no
Identity Provider can have access to the decryption key needed to decrypt your secrets (this also implies that Infisical requires entering the user's Master Password on top of authenticating with SSO).
## Identity providers
Infisical supports these and many other identity providers:

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

View File

@@ -7,26 +7,62 @@ Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Bitbucket">
Navigate to your project's integrations tab in Infisical.
<AccordionGroup>
<Accordion title="Push secrets to Bitbucket from Infisical">
<Steps>
<Step title="Authorize Infisical for Bitbucket">
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
![integrations](../../images/integrations.png)
Press on the Bitbucket tile and grant Infisical access to your Bitbucket account.
Press on the Bitbucket tile and grant Infisical access to your Bitbucket account.
![integrations bitbucket authorization](../../images/integrations/bitbucket/integrations-bitbucket-auth.png)
![integrations bitbucket authorization](../../images/integrations/bitbucket/integrations-bitbucket-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Bitbucket repo and press start integration to start syncing secrets to the repo.
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Bitbucket repo and press start integration to start syncing secrets to the repo.
![integrations bitbucket](../../images/integrations/bitbucket/integrations-bitbucket.png)
</Step>
</Steps>
![integrations bitbucket](../../images/integrations/bitbucket/integrations-bitbucket.png)
</Step>
</Steps>
</Accordion>
<Accordion title="Pull secrets in Bitbucket pipelines from Infisical">
<Steps>
<Step title="Configure Infisical Access">
Configure a [Machine Identity](https://infisical.com/docs/documentation/platform/identities/universal-auth) for your project and give it permissions to read secrets from your desired Infisical projects and environments.
</Step>
<Step title="Initialize Bitbucket variables">
Create Bitbucket variables (can be either workspace, repository, or deployment-level) to store Machine Identity Client ID and Client Secret.
![integrations bitbucket](../../images/integrations/bitbucket/integrations-bitbucket-env.png)
</Step>
<Step title="Integrate Infisical secrets into the pipeline">
Edit your Bitbucket pipeline YAML file to include the use of the Infisical CLI to fetch and inject secrets into any script or command within the pipeline.
#### Example
```yaml
image: atlassian/default-image:3
pipelines:
default:
- step:
name: Build application with secrets from Infisical
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=universal-auth --client-id=$INFISICAL_CLIENT_ID --client-secret=$INFISICAL_CLIENT_SECRET --silent --plain)
- infisical run --projectId=1d0443c1-cd43-4b3a-91a3-9d5f81254a89 --env=dev -- npm run build
```
<Tip>
Set the values of `projectId` and `env` flags in the `infisical run` command to your intended source path. For more options, refer to the CLI command reference [here](https://infisical.com/docs/cli/commands/run).
</Tip>
</Step>
</Steps>
</Accordion>
</AccordionGroup>

View File

@@ -27,12 +27,6 @@ Prerequisites:
![integrations terraform cloud authorization](../../images/integrations/terraform/integrations-terraformcloud-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
</Step>
<Step title="Start integration">
Select which Infisical environment secrets and Terraform Cloud variable type you want to sync to which Terraform Cloud workspace/project and press create integration to start syncing secrets to Terraform Cloud.
@@ -40,4 +34,4 @@ Prerequisites:
![integrations terraform cloud](../../images/integrations/terraform/integrations-terraformcloud-create.png)
![integrations terraform cloud](../../images/integrations/terraform/integrations-terraformcloud.png)
</Step>
</Steps>
</Steps>

View File

@@ -638,5 +638,10 @@
],
"integrations": {
"intercom": "hsg644ru"
},
"analytics": {
"koala": {
"publicApiKey": "pk_b50d7184e0e39ddd5cdb43cf6abeadd9b97d"
}
}
}

View File

@@ -19,6 +19,9 @@ From local development to production, Infisical SDKs provide the easiest way for
<Card href="/sdks/languages/java" title="Java" icon="java" color="#e41f23">
Manage secrets for your Java application on demand
</Card>
<Card href="/sdks/languages/go" title="Go icon="golang" color="#367B99">
Manage secrets for your Go application on demand
</Card>
<Card href="/sdks/languages/csharp" title="C#" icon="bars" color="#368833">
Manage secrets for your C#/.NET application on demand
</Card>

View File

@@ -6,6 +6,8 @@ import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { parseDocument, Scalar, YAMLMap } from "yaml";
import { SecretType } from "@app/hooks/api/types";
import Button from "../basic/buttons/Button";
import Error from "../basic/Error";
import { createNotification } from "../notifications";
@@ -33,7 +35,6 @@ const DropZone = ({
numCurrentRows
}: DropZoneProps) => {
const { t } = useTranslation();
const handleDragEnter = (e: DragEvent) => {
e.preventDefault();
@@ -66,7 +67,7 @@ const DropZone = ({
key,
value: keyPairs[key as keyof typeof keyPairs].value,
comment: keyPairs[key as keyof typeof keyPairs].comments.join("\n"),
type: "shared",
type: SecretType.Shared,
tags: []
}));
break;
@@ -79,7 +80,7 @@ const DropZone = ({
key,
value: keyPairs[key as keyof typeof keyPairs],
comment: "",
type: "shared",
type: SecretType.Shared,
tags: []
}));
break;
@@ -102,7 +103,7 @@ const DropZone = ({
key,
value: keyPairs[key as keyof typeof keyPairs]?.toString() ?? "",
comment,
type: "shared",
type: SecretType.Shared,
tags: []
};
});
@@ -132,7 +133,7 @@ const DropZone = ({
if (file === undefined) {
createNotification({
text: "You can't inject files from VS Code. Click 'Reveal in finder', and drag your file directly from the directory where it's located.",
type: "error",
type: "error"
});
setLoading(false);
return;

View File

@@ -1,5 +1,7 @@
import { SecretDataProps } from "public/data/frequentInterfaces";
import { SecretType } from "@app/hooks/api/types";
/**
* This function downloads the secrets as a .env file
* @param {object} obj
@@ -10,8 +12,8 @@ const checkOverrides = async ({ data }: { data: SecretDataProps[] }) => {
let secrets: SecretDataProps[] = data!.map((secret) => Object.create(secret));
const overridenSecrets = data!.filter((secret) =>
secret.valueOverride === undefined || secret?.value !== secret?.valueOverride
? "shared"
: "personal"
? SecretType.Shared
: SecretType.Personal
);
if (overridenSecrets.length) {
overridenSecrets.forEach((secret) => {

View File

@@ -3,6 +3,7 @@ import crypto from "crypto";
import { SecretDataProps, Tag } from "public/data/frequentInterfaces";
import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
import { SecretType } from "@app/hooks/api/types";
import { decryptAssymmetric, encryptSymmetric } from "../cryptography/crypto";
@@ -20,7 +21,7 @@ interface EncryptedSecretProps {
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
type: "personal" | "shared";
type: SecretType;
tags: Tag[];
}
@@ -108,8 +109,8 @@ const encryptSecrets = async ({
secretCommentTag,
type:
secret.valueOverride === undefined || secret?.value !== secret?.valueOverride
? "shared"
: "personal",
? SecretType.Shared
: SecretType.Personal,
tags: secret.tags
};

View File

@@ -15,7 +15,7 @@ const replaceContentWithDot = (str: string) => {
};
const syntaxHighlight = (content?: string | null, isVisible?: boolean, isImport?: boolean) => {
if (isImport) return "IMPORTED";
if (isImport && !content) return "IMPORTED";
if (content === "") return "EMPTY";
if (!content) return "EMPTY";
if (!isVisible) return replaceContentWithDot(content);

View File

@@ -279,7 +279,28 @@ export const useGetImportedSecretsAllEnvs = ({
[(secretImports || []).map((response) => response.data)]
);
return { secretImports, isImportedSecretPresentInEnv };
const getImportedSecretByKey = useCallback(
(envSlug: string, secretName: string) => {
const selectedEnvIndex = environments.indexOf(envSlug);
if (selectedEnvIndex !== -1) {
const secret = secretImports?.[selectedEnvIndex]?.data?.find(({ secrets }) =>
secrets.find((s) => s.key === secretName)
);
if (!secret) return undefined;
return {
secret: secret?.secrets.find((s) => s.key === secretName),
environmentInfo: secret?.environmentInfo
};
}
return undefined;
},
[(secretImports || []).map((response) => response.data)]
);
return { secretImports, isImportedSecretPresentInEnv, getImportedSecretByKey };
};
export const useGetImportedFoldersByEnv = ({

View File

@@ -7,7 +7,7 @@ import {
} from "@app/components/utilities/cryptography/crypto";
import { apiRequest } from "@app/config/request";
import { DecryptedSecret } from "../secrets/types";
import { DecryptedSecret, SecretType } from "../secrets/types";
import {
TGetSecretSnapshotsDTO,
TSecretRollbackDTO,
@@ -112,7 +112,7 @@ export const useGetSnapshotSecrets = ({ decryptFileKey, snapshotId }: TSnapshotD
version: encSecret.version
};
if (encSecret.type === "personal") {
if (encSecret.type === SecretType.Personal) {
personalSecrets[decryptedSecret.key] = { id: encSecret.secretId, value: secretValue };
} else {
sharedSecrets.push(decryptedSecret);

View File

@@ -14,6 +14,7 @@ import {
EncryptedSecret,
EncryptedSecretVersion,
GetSecretVersionsDTO,
SecretType,
TGetProjectSecretsAllEnvDTO,
TGetProjectSecretsDTO,
TGetProjectSecretsKey
@@ -77,7 +78,7 @@ export const decryptSecrets = (
skipMultilineEncoding: encSecret.skipMultilineEncoding
};
if (encSecret.type === "personal") {
if (encSecret.type === SecretType.Personal) {
personalSecrets[decryptedSecret.key] = {
id: encSecret.id,
value: secretValue

View File

@@ -1,11 +1,16 @@
import type { UserWsKeyPair } from "../keys/types";
import type { WsTag } from "../tags/types";
export enum SecretType {
Shared = "shared",
Personal = "personal"
}
export type EncryptedSecret = {
id: string;
version: number;
workspace: string;
type: "shared" | "personal";
type: SecretType;
environment: string;
secretKeyCiphertext: string;
secretKeyIV: string;
@@ -49,7 +54,7 @@ export type EncryptedSecretVersion = {
secretId: string;
version: number;
workspace: string;
type: string;
type: SecretType;
isDeleted: boolean;
envId: string;
secretKeyCiphertext: string;
@@ -101,14 +106,14 @@ export type TCreateSecretsV3DTO = {
secretPath: string;
workspaceId: string;
environment: string;
type: string;
type: SecretType;
};
export type TUpdateSecretsV3DTO = {
latestFileKey: UserWsKeyPair;
workspaceId: string;
environment: string;
type: string;
type: SecretType;
secretPath: string;
skipMultilineEncoding?: boolean;
newSecretName?: string;
@@ -124,7 +129,7 @@ export type TUpdateSecretsV3DTO = {
export type TDeleteSecretsV3DTO = {
workspaceId: string;
environment: string;
type: "shared" | "personal";
type: SecretType;
secretPath: string;
secretName: string;
secretId?: string;
@@ -140,7 +145,7 @@ export type TCreateSecretBatchDTO = {
secretValue: string;
secretComment: string;
skipMultilineEncoding?: boolean;
type: "shared" | "personal";
type: SecretType;
metadata?: {
source?: string;
};
@@ -153,7 +158,7 @@ export type TUpdateSecretBatchDTO = {
secretPath: string;
latestFileKey: UserWsKeyPair;
secrets: Array<{
type: "shared" | "personal";
type: SecretType;
secretName: string;
skipMultilineEncoding?: boolean;
secretValue: string;
@@ -168,14 +173,14 @@ export type TDeleteSecretBatchDTO = {
secretPath: string;
secrets: Array<{
secretName: string;
type: "shared" | "personal";
type: SecretType;
}>;
};
export type CreateSecretDTO = {
workspaceId: string;
environment: string;
type: "shared" | "personal";
type: SecretType;
secretKey: string;
secretKeyCiphertext: string;
secretKeyIV: string;

View File

@@ -47,7 +47,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, useSubscription } from
import { interpolateSecrets } from "@app/helpers/secret";
import { usePopUp } from "@app/hooks";
import { useCreateFolder, useDeleteSecretBatch, useGetUserWsKey } from "@app/hooks/api";
import { DecryptedSecret, TImportedSecrets, WsTag } from "@app/hooks/api/types";
import { DecryptedSecret, SecretType, TImportedSecrets, WsTag } from "@app/hooks/api/types";
import { debounce } from "@app/lib/fn/debounce";
import {
@@ -211,7 +211,7 @@ export const ActionBar = ({
secretPath,
workspaceId,
environment,
secrets: bulkDeletedSecrets.map(({ key }) => ({ secretName: key, type: "shared" }))
secrets: bulkDeletedSecrets.map(({ key }) => ({ secretName: key, type: SecretType.Shared }))
});
resetSelectedSecret();
handlePopUpClose("bulkDeleteSecrets");

View File

@@ -6,7 +6,7 @@ import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { useCreateSecretV3 } from "@app/hooks/api";
import { UserWsKeyPair } from "@app/hooks/api/types";
import { SecretType, UserWsKeyPair } from "@app/hooks/api/types";
import { PopUpNames, usePopUpAction, usePopUpState } from "../../SecretMainPage.store";
@@ -56,7 +56,7 @@ export const CreateSecretForm = ({
secretName: key,
secretValue: value || "",
secretComment: "",
type: "shared",
type: SecretType.Shared,
latestFileKey: decryptFileKey
});
closePopUp(PopUpNames.CreateSecretForm);

View File

@@ -16,7 +16,7 @@ import { usePopUp, useToggle } from "@app/hooks";
import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api";
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
import { secretKeys } from "@app/hooks/api/secrets/queries";
import { DecryptedSecret, UserWsKeyPair } from "@app/hooks/api/types";
import { DecryptedSecret, SecretType, UserWsKeyPair } from "@app/hooks/api/types";
import { PopUpNames, usePopUpAction } from "../../SecretMainPage.store";
import { CopySecretsFromBoard } from "./CopySecretsFromBoard";
@@ -170,7 +170,7 @@ export const SecretDropzone = ({
workspaceId,
environment,
secrets: Object.entries(create).map(([secretName, secData]) => ({
type: "shared",
type: SecretType.Shared,
secretComment: secData.comments.join("\n"),
secretValue: secData.value,
secretName
@@ -184,7 +184,7 @@ export const SecretDropzone = ({
workspaceId,
environment,
secrets: Object.entries(update).map(([secretName, secData]) => ({
type: "shared",
type: SecretType.Shared,
secretComment: secData.comments.join("\n"),
secretValue: secData.value,
secretName

View File

@@ -10,7 +10,7 @@ import { usePopUp } from "@app/hooks";
import { useCreateSecretV3, useDeleteSecretV3, useUpdateSecretV3 } from "@app/hooks/api";
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
import { secretKeys } from "@app/hooks/api/secrets/queries";
import { DecryptedSecret } from "@app/hooks/api/secrets/types";
import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types";
import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries";
import { UserWsKeyPair, WsTag } from "@app/hooks/api/types";
@@ -119,7 +119,7 @@ export const SecretListView = ({
const handleSecretOperation = async (
operation: "create" | "update" | "delete",
type: "shared" | "personal",
type: SecretType,
key: string,
{
value,
@@ -227,23 +227,25 @@ export const SecretListView = ({
try {
// personal secret change
if (overrideAction === "deleted") {
await handleSecretOperation("delete", "personal", oldKey, {
await handleSecretOperation("delete", SecretType.Personal, oldKey, {
secretId: orgSecret.idOverride
});
} else if (overrideAction && idOverride) {
await handleSecretOperation("update", "personal", oldKey, {
await handleSecretOperation("update", SecretType.Personal, oldKey, {
value: valueOverride,
newKey: hasKeyChanged ? key : undefined,
secretId: orgSecret.idOverride,
skipMultilineEncoding: modSecret.skipMultilineEncoding
});
} else if (overrideAction) {
await handleSecretOperation("create", "personal", oldKey, { value: valueOverride });
await handleSecretOperation("create", SecretType.Personal, oldKey, {
value: valueOverride
});
}
// shared secret change
if (!isSharedSecUnchanged) {
await handleSecretOperation("update", "shared", oldKey, {
await handleSecretOperation("update", SecretType.Shared, oldKey, {
value,
tags: tagIds,
comment,
@@ -286,7 +288,7 @@ export const SecretListView = ({
const handleSecretDelete = useCallback(async () => {
const { key, id: secretId } = popUp.deleteSecret?.data as DecryptedSecret;
try {
await handleSecretOperation("delete", "shared", key, { secretId });
await handleSecretOperation("delete", SecretType.Shared, key, { secretId });
// wrap this in another function and then reuse
queryClient.invalidateQueries(
secretKeys.getProjectSecret({ workspaceId, environment, secretPath })

View File

@@ -64,7 +64,7 @@ import {
} from "@app/hooks/api";
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
import { TSecretFolder } from "@app/hooks/api/types";
import { SecretType, TSecretFolder } from "@app/hooks/api/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { FolderForm } from "../SecretMainPage/components/ActionBar/FolderForm";
@@ -188,7 +188,7 @@ export const SecretOverviewPage = () => {
environments: userAvailableEnvs.map(({ slug }) => slug)
});
const { isImportedSecretPresentInEnv } = useGetImportedSecretsAllEnvs({
const { isImportedSecretPresentInEnv, getImportedSecretByKey } = useGetImportedSecretsAllEnvs({
projectId: workspaceId,
decryptFileKey: latestFileKey!,
path: secretPath,
@@ -320,7 +320,7 @@ export const SecretOverviewPage = () => {
secretName: key,
secretValue: value,
secretComment: "",
type: "shared",
type: SecretType.Shared,
latestFileKey: latestFileKey!
});
createNotification({
@@ -344,7 +344,13 @@ export const SecretOverviewPage = () => {
}
};
const handleSecretUpdate = async (env: string, key: string, value: string, secretId?: string) => {
const handleSecretUpdate = async (
env: string,
key: string,
value: string,
type = SecretType.Shared,
secretId?: string
) => {
try {
await updateSecretV3({
environment: env,
@@ -353,7 +359,7 @@ export const SecretOverviewPage = () => {
secretId,
secretName: key,
secretValue: value,
type: "shared",
type,
latestFileKey: latestFileKey!
});
createNotification({
@@ -377,7 +383,7 @@ export const SecretOverviewPage = () => {
secretPath,
secretName: key,
secretId,
type: "shared"
type: SecretType.Shared
});
createNotification({
type: "success",
@@ -807,6 +813,7 @@ export const SecretOverviewPage = () => {
isSelected={selectedEntries.secret[key]}
onToggleSecretSelect={() => toggleSelectedEntry(EntryType.SECRET, key)}
secretPath={secretPath}
getImportedSecretByKey={getImportedSecretByKey}
isImportedSecretPresentInEnv={isImportedSecretPresentInEnv}
onSecretCreate={handleSecretCreate}
onSecretDelete={handleSecretDelete}

View File

@@ -18,7 +18,7 @@ import {
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { useWorkspace } from "@app/context";
import { useCreateFolder, useCreateSecretV3, useUpdateSecretV3 } from "@app/hooks/api";
import { DecryptedSecret, UserWsKeyPair } from "@app/hooks/api/types";
import { DecryptedSecret, SecretType, UserWsKeyPair } from "@app/hooks/api/types";
const typeSchema = z
.object({
@@ -103,7 +103,7 @@ export const CreateSecretForm = ({
secretPath,
secretName: key,
secretValue: value || "",
type: "shared",
type: SecretType.Shared,
latestFileKey: decryptFileKey
});
}
@@ -115,7 +115,7 @@ export const CreateSecretForm = ({
secretName: key,
secretValue: value || "",
secretComment: "",
type: "shared",
type: SecretType.Shared,
latestFileKey: decryptFileKey
});
});

View File

@@ -10,24 +10,33 @@ import { IconButton, Tooltip } from "@app/components/v2";
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { useToggle } from "@app/hooks";
import { SecretType } from "@app/hooks/api/types";
type Props = {
defaultValue?: string | null;
secretName: string;
secretId?: string;
isOverride?: boolean;
isCreatable?: boolean;
isVisible?: boolean;
isImportedSecret: boolean;
environment: string;
secretPath: string;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise<void>;
onSecretUpdate: (
env: string,
key: string,
value: string,
type?: SecretType,
secretId?: string
) => Promise<void>;
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
};
export const SecretEditRow = ({
defaultValue,
isCreatable,
isOverride,
isImportedSecret,
onSecretUpdate,
secretName,
@@ -73,7 +82,13 @@ export const SecretEditRow = ({
if (isCreatable) {
await onSecretCreate(environment, secretName, value);
} else {
await onSecretUpdate(environment, secretName, value, secretId);
await onSecretUpdate(
environment,
secretName,
value,
isOverride ? SecretType.Personal : SecretType.Shared,
secretId
);
}
}
reset({ value });
@@ -93,12 +108,13 @@ export const SecretEditRow = ({
<div className="group flex w-full cursor-text items-center space-x-2">
<div className="flex-grow border-r border-r-mineshaft-600 pr-2 pl-1">
<Controller
disabled={isImportedSecret}
disabled={isImportedSecret && !defaultValue}
control={control}
name="value"
render={({ field }) => (
<InfisicalSecretInput
{...field}
isReadOnly={isImportedSecret}
value={field.value as string}
key="secret-input"
isVisible={isVisible}

View File

@@ -13,7 +13,8 @@ import { twMerge } from "tailwind-merge";
import { Button, Checkbox, TableContainer, Td, Tooltip, Tr } from "@app/components/v2";
import { useToggle } from "@app/hooks";
import { DecryptedSecret } from "@app/hooks/api/secrets/types";
import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
import { SecretEditRow } from "./SecretEditRow";
import SecretRenameRow from "./SecretRenameRow";
@@ -27,9 +28,19 @@ type Props = {
onToggleSecretSelect: (key: string) => void;
getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise<void>;
onSecretUpdate: (
env: string,
key: string,
value: string,
type?: SecretType,
secretId?: string
) => Promise<void>;
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
isImportedSecretPresentInEnv: (env: string, secretName: string) => boolean;
getImportedSecretByKey: (
env: string,
secretName: string
) => { secret?: DecryptedSecret; environmentInfo?: WorkspaceEnv } | undefined;
};
export const SecretOverviewTableRow = ({
@@ -41,6 +52,7 @@ export const SecretOverviewTableRow = ({
onSecretCreate,
onSecretDelete,
isImportedSecretPresentInEnv,
getImportedSecretByKey,
expandableColWidth,
onToggleSecretSelect,
isSelected
@@ -53,8 +65,9 @@ export const SecretOverviewTableRow = ({
<>
<Tr isHoverable isSelectable onClick={() => setIsFormExpanded.toggle()} className="group">
<Td
className={`sticky left-0 z-10 bg-mineshaft-800 bg-clip-padding py-0 px-0 group-hover:bg-mineshaft-700 ${isFormExpanded && "border-t-2 border-mineshaft-500"
}`}
className={`sticky left-0 z-10 bg-mineshaft-800 bg-clip-padding py-0 px-0 group-hover:bg-mineshaft-700 ${
isFormExpanded && "border-t-2 border-mineshaft-500"
}`}
>
<div className="h-full w-full border-r border-mineshaft-600 py-2.5 px-5">
<div className="flex items-center space-x-5">
@@ -107,8 +120,8 @@ export const SecretOverviewTableRow = ({
isSecretPresent
? "Present secret"
: isSecretImported
? "Imported secret"
: "Missing secret"
? "Imported secret"
: "Missing secret"
}
>
<FontAwesomeIcon
@@ -132,8 +145,9 @@ export const SecretOverviewTableRow = ({
<Tr>
<Td
colSpan={totalCols}
className={`bg-bunker-600 px-0 py-0 ${isFormExpanded && "border-b-2 border-mineshaft-500"
}`}
className={`bg-bunker-600 px-0 py-0 ${
isFormExpanded && "border-b-2 border-mineshaft-500"
}`}
>
<div
className="ml-2 p-2"
@@ -179,6 +193,7 @@ export const SecretOverviewTableRow = ({
const isCreatable = !secret;
const isImportedSecret = isImportedSecretPresentInEnv(slug, secretKey);
const importedSecret = getImportedSecretByKey(slug, secretKey);
return (
<tr
@@ -189,8 +204,15 @@ export const SecretOverviewTableRow = ({
className="flex h-full items-center"
style={{ padding: "0.25rem 1rem" }}
>
<div title={name} className="flex h-8 w-[8rem] items-center ">
<div title={name} className="flex h-8 w-[8rem] items-center space-x-2 ">
<span className="truncate">{name}</span>
{isImportedSecret && (
<Tooltip
content={`Imported secret from the '${importedSecret?.environmentInfo?.name}' environment`}
>
<FontAwesomeIcon icon={faFileImport} />
</Tooltip>
)}
</div>
</td>
<td className="col-span-2 h-8 w-full">
@@ -198,8 +220,13 @@ export const SecretOverviewTableRow = ({
secretPath={secretPath}
isVisible={isSecretVisible}
secretName={secretKey}
defaultValue={secret?.value}
defaultValue={
secret?.valueOverride ||
secret?.value ||
importedSecret?.secret?.value
}
secretId={secret?.id}
isOverride={Boolean(secret?.valueOverride)}
isImportedSecret={isImportedSecret}
isCreatable={isCreatable}
onSecretDelete={onSecretDelete}

View File

@@ -18,7 +18,7 @@ import {
} from "@app/context";
import { useToggle } from "@app/hooks";
import { useGetUserWsKey, useUpdateSecretV3 } from "@app/hooks/api";
import { DecryptedSecret } from "@app/hooks/api/types";
import { DecryptedSecret, SecretType } from "@app/hooks/api/types";
import { SecretActionType } from "@app/views/SecretMainPage/components/SecretListView/SecretListView.utils";
type Props = {
@@ -37,7 +37,6 @@ type TFormSchema = z.infer<typeof formSchema>;
function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }: Props) {
const { currentWorkspace } = useWorkspace();
const { permission } = useProjectPermission();
const secrets = environments.map((env) => getSecretByKey(env.slug, secretKey));
@@ -113,7 +112,7 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
secretName: secret.key,
secretId: secret.id,
secretValue: secret.value || "",
type: "shared",
type: SecretType.Shared,
latestFileKey: decryptFileKey!,
tags: secret.tags.map((tag) => tag.id),
secretComment: secret.comment,

View File

@@ -13,7 +13,12 @@ import {
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteFolder, useDeleteSecretBatch } from "@app/hooks/api";
import { DecryptedSecret, TDeleteSecretBatchDTO, TSecretFolder } from "@app/hooks/api/types";
import {
DecryptedSecret,
SecretType,
TDeleteSecretBatchDTO,
TSecretFolder
} from "@app/hooks/api/types";
export enum EntryType {
FOLDER = "folder",
@@ -100,7 +105,7 @@ export const SelectionPanel = ({
...accum,
{
secretName: entry.key,
type: "shared" as "shared"
type: SecretType.Shared
}
];
}