diff --git a/backend/src/lib/knex/index.ts b/backend/src/lib/knex/index.ts index d43d2af8e..55d4bf399 100644 --- a/backend/src/lib/knex/index.ts +++ b/backend/src/lib/knex/index.ts @@ -2,6 +2,8 @@ import { Knex } from "knex"; import { Tables } from "knex/types/tables"; +import { TableName } from "@app/db/schemas"; + import { DatabaseError } from "../errors"; import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic"; @@ -25,28 +27,41 @@ export type TFindFilter = Partial & { $search?: Partial<{ [k in keyof R]: R[k] }>; $complex?: TKnexDynamicOperator; }; + export const buildFindFilter = - ({ $in, $notNull, $search, $complex, ...filter }: TFindFilter) => + ( + { $in, $notNull, $search, $complex, ...filter }: TFindFilter, + tableName?: TableName, + excludeKeys?: Array + ) => (bd: Knex.QueryBuilder) => { - void bd.where(filter); + const processedFilter = tableName + ? Object.fromEntries( + Object.entries(filter) + .filter(([key]) => !excludeKeys || !excludeKeys.includes(key as keyof R)) + .map(([key, value]) => [`${tableName}.${key}`, value]) + ) + : filter; + + void bd.where(processedFilter); if ($in) { Object.entries($in).forEach(([key, val]) => { if (val) { - void bd.whereIn(key as never, val as never); + void bd.whereIn([`${tableName ? `${tableName}.` : ""}${key}`] as never, val as never); } }); } if ($notNull?.length) { $notNull.forEach((key) => { - void bd.whereNotNull(key as never); + void bd.whereNotNull([`${tableName ? `${tableName}.` : ""}${key as string}`] as never); }); } if ($search) { Object.entries($search).forEach(([key, val]) => { if (val) { - void bd.whereILike(key as never, val as never); + void bd.whereILike([`${tableName ? `${tableName}.` : ""}${key}`] as never, val as never); } }); } diff --git a/backend/src/lib/validator/validate-url.ts b/backend/src/lib/validator/validate-url.ts index b555869d7..8f195e0b5 100644 --- a/backend/src/lib/validator/validate-url.ts +++ b/backend/src/lib/validator/validate-url.ts @@ -15,13 +15,13 @@ export const blockLocalAndPrivateIpAddresses = async (url: string) => { const validUrl = new URL(url); const inputHostIps: string[] = []; - if (isIPv4(validUrl.host)) { - inputHostIps.push(validUrl.host); + if (isIPv4(validUrl.hostname)) { + inputHostIps.push(validUrl.hostname); } else { - if (validUrl.host === "localhost" || validUrl.host === "host.docker.internal") { + if (validUrl.hostname === "localhost" || validUrl.hostname === "host.docker.internal") { throw new BadRequestError({ message: "Local IPs not allowed as URL" }); } - const resolvedIps = await dns.resolve4(validUrl.host); + const resolvedIps = await dns.resolve4(validUrl.hostname); inputHostIps.push(...resolvedIps); } const isInternalIp = inputHostIps.some((el) => isPrivateIp(el)); diff --git a/backend/src/services/secret-sync/teamcity/teamcity-sync-fns.ts b/backend/src/services/secret-sync/teamcity/teamcity-sync-fns.ts index 323f59851..6dbd9bdd7 100644 --- a/backend/src/services/secret-sync/teamcity/teamcity-sync-fns.ts +++ b/backend/src/services/secret-sync/teamcity/teamcity-sync-fns.ts @@ -10,7 +10,7 @@ import { TTeamCitySyncWithCredentials } from "@app/services/secret-sync/teamcity/teamcity-sync-types"; -// Note: Most variables won't be returned with a value due to them being a "password" type (starting with "env."). +// Note: Most variables won't be returned with a value due to them being a "password" type. // TeamCity API returns empty string for password-type variables for security reasons. const listTeamCityVariables = async ({ instanceUrl, accessToken, project, buildConfig }: TTeamCityListVariables) => { const { data } = await request.get( @@ -25,12 +25,16 @@ const listTeamCityVariables = async ({ instanceUrl, accessToken, project, buildC } ); + // Filters for only non-inherited environment variables // Strips out "env." from map key, but the "name" field still has the original unaltered key. return Object.fromEntries( - data.property.map((variable) => [ - variable.name.startsWith("env.") ? variable.name.substring(4) : variable.name, - { ...variable, value: variable.value || "" } // Password values will be empty strings from the API for security - ]) + data.property + .filter((variable) => !variable.inherited) + .filter((variable) => variable.name.startsWith("env.")) + .map((variable) => [ + variable.name.substring(4), + { ...variable, value: variable.value || "" } // Password values will be empty strings from the API for security + ]) ); }; 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 64970b610..cd2773172 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 @@ -64,7 +64,8 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => { const findOne = async (filter: Partial, tx?: Knex) => { try { const docs = await (tx || db)(TableName.SecretV2) - .where(filter) + // eslint-disable-next-line @typescript-eslint/no-misused-promises + .where(buildFindFilter(filter, TableName.SecretV2)) .leftJoin( TableName.SecretV2JnTag, `${TableName.SecretV2}.id`, 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 5c2f6a2f0..6fdcadeff 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 @@ -2,7 +2,7 @@ import path from "node:path"; import RE2 from "re2"; -import { TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas"; +import { SecretType, TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas"; import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; import { logger } from "@app/lib/logger"; @@ -720,7 +720,7 @@ export const reshapeBridgeSecret = ( secretReminderRecipients: secret.secretReminderRecipients || [], ...(secretValueHidden ? { - secretValue: INFISICAL_SECRET_VALUE_HIDDEN_MASK, + secretValue: secret.type === SecretType.Personal ? secret.value : INFISICAL_SECRET_VALUE_HIDDEN_MASK, secretValueHidden: true } : { diff --git a/docs/documentation/platform/access-controls/assume-privilege.mdx b/docs/documentation/platform/access-controls/assume-privilege.mdx new file mode 100644 index 000000000..a38fd65f0 --- /dev/null +++ b/docs/documentation/platform/access-controls/assume-privilege.mdx @@ -0,0 +1,40 @@ +--- +title: "Assume Privileges" +description: "Learn how to temporarily assume the privileges of a user or machine identity within a project." +--- + +This feature allows authorized users to temporarily take on the permissions of another user or identity. It helps administrators and access managers test and verify permissions before granting access, ensuring everything is set up correctly. +It also reduces back-and-forth with end users when troubleshooting permission-related issues. + +## How It Works + +When an authorized user activates assume privileges mode, they temporarily inherit the target user or identity’s permissions for up to one hour. +During this time, they can perform actions within the system with the same level of access as the target user. + +- **Permission-based**: Only permissions are inherited, not the full identity +- **Time-limited**: Access automatically expires after one hour +- **Audited**: All actions are logged under the original user's account. This means any action taken during the session will be recorded under the entity assuming the privileges, not the target entity. +- **Authorization required**: Only users with the specific **assume privilege** permission can use this feature +- **Scoped to a single project**: You can only assume privileges for one project at a time + +## How to Assume Privileges + + + + Click on the user or identity you want to assume. + + ![Access control page](/images/platform/access-controls/assume-privileges/access-control.png) + + + + Click **Assume Privilege**, then type `assume` to confirm and start your session. + + ![Access control detail page](/images/platform/access-controls/assume-privileges/access-control-detail.png) + + + + You will see a yellow banner indicating that your assume privilege session is active. You can exit at any time by clicking **Exit**. + + ![session start](/images/platform/access-controls/assume-privileges/session-start.png) + + \ No newline at end of file diff --git a/docs/documentation/platform/kms-configuration/aws-kms.mdx b/docs/documentation/platform/kms-configuration/aws-kms.mdx index 3fc5404ae..b4631b1c3 100644 --- a/docs/documentation/platform/kms-configuration/aws-kms.mdx +++ b/docs/documentation/platform/kms-configuration/aws-kms.mdx @@ -9,6 +9,9 @@ This guide will walk you through the steps needed to configure external KMS supp ## Prerequisites +- An AWS KMS Key configured as a `Symmetric` key and with `Encrypt and Decrypt` key usage. + ![Create AWS KMS Key](/images/platform/kms/aws/aws-kms-key-create.png) + Before you begin, you'll first need to choose a method of authentication with AWS from below. diff --git a/docs/documentation/platform/kms/hsm-integration.mdx b/docs/documentation/platform/kms/hsm-integration.mdx index 633377b3d..a9ab2c832 100644 --- a/docs/documentation/platform/kms/hsm-integration.mdx +++ b/docs/documentation/platform/kms/hsm-integration.mdx @@ -268,11 +268,11 @@ For organizations that work with US government agencies, FIPS compliance is almo - When using Kubernetes, you need to mount the path containing the HSM client files. This section covers how to configure your Infisical instance to use an HSM with Kubernetes. + When using Kubernetes, you need to mount the path containing the HSM client files. This section covers how to configure your Infisical instance to use an HSM with Kubernetes. In this example, we are going to be using `/etc/luna-docker`. ```bash - mkdir /etc/hsm-client + mkdir /etc/luna-docker ``` After [setting up your Luna Cloud HSM client](https://thalesdocs.com/gphsm/luna/7/docs/network/Content/install/client_install/add_dpod.htm), you should have a set of files, referred to as the HSM client. You don't need all the files, but for simplicity we recommend copying all the files from the client. @@ -306,20 +306,60 @@ For organizations that work with US government agencies, FIPS compliance is almo The most important parts of the client folder is the `Chrystoki.conf` file, and the `libs`, `plugins`, and `jsp` folders. You need to copy these files to the folder you created in the first step. ```bash - cp -r / /etc/hsm-client + cp -r //* /etc/luna-docker ``` + + + The `/*` wildcard will copy all files and folders within the HSM client. The wildcard is important to ensure that the file structure is inline with the rest of this guide. + + + After copying the files, the `/etc/luna-docker` directory should have the following file structure: + ```bash + $ ls -R /etc/luna-docker + Chrystoki.conf etc lock server-certificate.pem + Chrystoki.conf.tmp2E jsp partition-ca-certificate.pem setenv + lch-support-linux-64bit partition-certificate.pem + bin libs plugins + + /etc/luna-docker/bin: + 64 + + /etc/luna-docker/bin/64: + ckdemo cmu lunacm multitoken vtl + + /etc/luna-docker/etc: + openssl.cnf + + /etc/luna-docker/jsp: + 64 LunaProvider.jar + + /etc/luna-docker/jsp/64: + libLunaAPI.so + + /etc/luna-docker/libs: + 64 + + /etc/luna-docker/libs/64: + libCryptoki2.so + + /etc/luna-docker/lock: + + /etc/luna-docker/plugins: + libcloud.plugin + ``` + The `Chrystoki.conf` file is used to configure the HSM client. You need to update the `Chrystoki.conf` file to point to the correct file paths. - In this example, we will be mounting the `/etc/hsm-client` folder from the host to containers in our deployment's pods at the path `/hsm-client`. This means the contents of `/etc/hsm-client` on the host will be accessible at `/hsm-client` within the containers. + In this example, we will be mounting the `/etc/luna-docker` folder from the host to containers in our deployment's pods at the path `/usr/safenet/lunaclient`. This means the contents of `/etc/luna-docker` on the host will be accessible at `/usr/safenet/lunaclient` within the containers. An example config file will look like this: ```Chrystoki.conf Chrystoki2 = { - # This path points to the mounted path, /hsm-client - LibUNIX64 = /hsm-client/libs/64/libCryptoki2.so; + # This path points to the mounted path, /usr/safenet/lunaclient + LibUNIX64 = /usr/safenet/lunaclient/libs/64/libCryptoki2.so; } Luna = { @@ -339,8 +379,8 @@ For organizations that work with US government agencies, FIPS compliance is almo Misc = { # Update the paths to point to the mounted path if your folder structure is different from the one mentioned in the previous step. - PluginModuleDir = /hsm-client/plugins; - MutexFolder = /hsm-client/lock; + PluginModuleDir = /usr/safenet/lunaclient/plugins; + MutexFolder = /usr/safenet/lunaclient/lock; PE1746Enabled = 1; ToolsDir = /usr/bin; @@ -353,7 +393,7 @@ For organizations that work with US government agencies, FIPS compliance is almo LunaSA Client = { ReceiveTimeout = 20000; # Update the paths to point to the mounted path if your folder structure is different from the one mentioned in the previous step. - SSLConfigFile = /hsm-client/etc/openssl.cnf; + SSLConfigFile = /usr/safenet/lunaclient/etc/openssl.cnf; ClientPrivKeyFile = ./etc/ClientNameKey.pem; ClientCertFile = ./etc/ClientNameCert.pem; ServerCAFile = ./etc/CAFile.pem; @@ -441,7 +481,7 @@ For organizations that work with US government agencies, FIPS compliance is almo ```bash kubectl exec hsm-setup-pod -- mkdir -p /data/ # Create the data directory - kubectl cp ./hsm-client/ hsm-setup-pod:/data/ # Copy the HSM client files into the PVC + kubectl cp /etc/luna-docker/. hsm-setup-pod:/data/ # Copy the HSM client files into the PVC kubectl exec hsm-setup-pod -- chmod -R 755 /data/ # Set the correct permissions for the HSM client files ``` @@ -456,7 +496,7 @@ For organizations that work with US government agencies, FIPS compliance is almo Next we need to update the environment variables used for the deployment. If you followed the [setup instructions for Kubernetes deployments](/self-hosting/deployment-options/kubernetes-helm), you should have a Kubernetes secret called `infisical-secrets`. We need to update the secret with the following environment variables: - - `HSM_LIB_PATH` - The path to the HSM client library _(mapped to `/hsm-client/libs/64/libCryptoki2.so`)_ + - `HSM_LIB_PATH` - The path to the HSM client library _(mapped to `/usr/safenet/lunaclient/libs/64/libCryptoki2.so`)_ - `HSM_PIN` - The PIN for the HSM device that you created when setting up your Luna Cloud HSM client - `HSM_SLOT` - The slot number for the HSM device that you selected when setting up your Luna Cloud HSM client - `HSM_KEY_LABEL` - The label for the HSM key. If no key is found with the provided key label, the HSM will create a new key with the provided label. @@ -471,7 +511,7 @@ For organizations that work with US government agencies, FIPS compliance is almo type: Opaque stringData: # ... Other environment variables ... - HSM_LIB_PATH: "/hsm-client/libs/64/libCryptoki2.so" # If you followed this guide, this will be the path of the Luna Cloud HSM client + HSM_LIB_PATH: "/usr/safenet/lunaclient/libs/64/libCryptoki2.so" # If you followed this guide, this will be the path of the Luna Cloud HSM client HSM_PIN: "" HSM_SLOT: "" HSM_KEY_LABEL: "" @@ -487,7 +527,7 @@ For organizations that work with US government agencies, FIPS compliance is almo After we've successfully configured the PVC and updated our environment variables, we are ready to update the deployment configuration so that the pods it creates can access the HSM client files. - We need to update the Docker image of the deployment to use `infisical/infisical-fips`. The `infisical/infisical-fips` image is a functionally identical image to the `infisical/infisical` image, but it is built with support for HSM encryption. + We need to update the Docker image of the deployment to use `infisical/infisical-fips`. The `infisical/infisical-fips` image is a functionally identical image to the `infisical/infisical` image, but it is built with HSM support. ```yaml # ... The rest of the values.yaml file ... @@ -499,8 +539,7 @@ For organizations that work with US government agencies, FIPS compliance is almo extraVolumeMounts: - name: hsm-data - mountPath: /hsm-client # The path we will mount the HSM client files to - subPath: ./hsm-client + mountPath: /usr/safenet/lunaclient # The path we will mount the HSM client files to extraVolumes: - name: hsm-data diff --git a/docs/images/platform/access-controls/assume-privileges/access-control-detail.png b/docs/images/platform/access-controls/assume-privileges/access-control-detail.png new file mode 100644 index 000000000..e0844b8f4 Binary files /dev/null and b/docs/images/platform/access-controls/assume-privileges/access-control-detail.png differ diff --git a/docs/images/platform/access-controls/assume-privileges/access-control.png b/docs/images/platform/access-controls/assume-privileges/access-control.png new file mode 100644 index 000000000..aa6974cdd Binary files /dev/null and b/docs/images/platform/access-controls/assume-privileges/access-control.png differ diff --git a/docs/images/platform/access-controls/assume-privileges/session-start.png b/docs/images/platform/access-controls/assume-privileges/session-start.png new file mode 100644 index 000000000..1aab112c4 Binary files /dev/null and b/docs/images/platform/access-controls/assume-privileges/session-start.png differ diff --git a/docs/images/platform/kms/aws/aws-kms-key-create.png b/docs/images/platform/kms/aws/aws-kms-key-create.png new file mode 100644 index 000000000..7d8466538 Binary files /dev/null and b/docs/images/platform/kms/aws/aws-kms-key-create.png differ diff --git a/docs/integrations/secret-syncs/teamcity.mdx b/docs/integrations/secret-syncs/teamcity.mdx index e79fc0f0c..af4c8d76a 100644 --- a/docs/integrations/secret-syncs/teamcity.mdx +++ b/docs/integrations/secret-syncs/teamcity.mdx @@ -34,7 +34,7 @@ description: "Learn how to configure a TeamCity Sync for Infisical." - **Build Configuration**: The build configuration to sync secrets to. - Not including a Build Configuration will sync secrets to the entire project. + Not including a Build Configuration will sync secrets to the project. 5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**. @@ -44,6 +44,11 @@ description: "Learn how to configure a TeamCity Sync for Infisical." - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over TeamCity when keys conflict. - **Import Secrets (Prioritize TeamCity)**: Imports secrets from the destination endpoint before syncing, prioritizing values from TeamCity over Infisical when keys conflict. + + + Infisical only syncs secrets from within the target scope; inherited secrets will not be imported. + + - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. diff --git a/docs/mint.json b/docs/mint.json index 062ec5916..fcde10fa7 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -160,6 +160,7 @@ }, "documentation/platform/access-controls/additional-privileges", "documentation/platform/access-controls/temporary-access", + "documentation/platform/access-controls/assume-privilege", "documentation/platform/access-controls/access-requests", "documentation/platform/access-controls/project-access-requests", "documentation/platform/pr-workflows", @@ -889,8 +890,8 @@ ] }, { - "group": "LDAP Password", - "pages": [ + "group": "LDAP Password", + "pages": [ "api-reference/endpoints/secret-rotations/ldap-password/create", "api-reference/endpoints/secret-rotations/ldap-password/delete", "api-reference/endpoints/secret-rotations/ldap-password/get-by-id", diff --git a/frontend/src/components/v2/SecretInput/SecretInput.tsx b/frontend/src/components/v2/SecretInput/SecretInput.tsx index 96f79e65f..c8b8f2ee6 100644 --- a/frontend/src/components/v2/SecretInput/SecretInput.tsx +++ b/frontend/src/components/v2/SecretInput/SecretInput.tsx @@ -93,6 +93,7 @@ export const SecretInput = forwardRef( onFocus={(evt) => { onFocus?.(evt); setIsSecretFocused.on(); + evt.currentTarget.select(); }} disabled={isDisabled} spellCheck={false} diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx index ddb9a99d1..bd7660838 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx @@ -4,6 +4,7 @@ import { subject } from "@casl/ability"; import { faCheck, faCopy, + faEyeSlash, faProjectDiagram, faTrash, faXmark @@ -25,7 +26,6 @@ import { ModalTrigger, Tooltip } from "@app/components/v2"; -import { Blur } from "@app/components/v2/Blur"; import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput"; import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context"; import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types"; @@ -124,7 +124,13 @@ export const SecretEditRow = ({ ); } } - reset({ value }); + if (secretValueHidden && !isOverride) { + setTimeout(() => { + reset({ value: defaultValue || null }); + }, 50); + } else { + reset({ value }); + } }; const canReadSecretValue = hasSecretReadValueOrDescribePermission( @@ -132,6 +138,16 @@ export const SecretEditRow = ({ ProjectPermissionSecretActions.ReadValue ); + const canEditSecretValue = permission.can( + ProjectPermissionSecretActions.Edit, + subject(ProjectPermissionSub.Secrets, { + environment, + secretPath, + secretName, + secretTags: ["*"] + }) + ); + const handleDeleteSecret = useCallback(async () => { setIsDeleting.on(); setIsModalOpen(false); @@ -153,29 +169,32 @@ export const SecretEditRow = ({ deleteKey={secretName} onDeleteApproved={handleDeleteSecret} /> - + {secretValueHidden && !isOverride && ( + + + + )}
- {secretValueHidden ? ( - - ) : ( - ( - - )} - /> - )} + ( + + )} + />
{ + const canEditSecretValue = permission.can( + ProjectPermissionSecretActions.Edit, + subject(ProjectPermissionSub.Secrets, { + environment: secret?.env || "", + secretPath: secret?.path || "", + secretName: secret?.key || "", + secretTags: ["*"] + }) + ); + + if (secret?.secretValueHidden && !secret?.valueOverride) { + return canEditSecretValue ? "******" : ""; + } + return secret?.valueOverride || secret?.value || importedSecret?.secret?.value || ""; + }; + return ( <> setIsFormExpanded.toggle()} className="group"> @@ -228,13 +256,7 @@ export const SecretOverviewTableRow = ({ isVisible={isSecretVisible} secretName={secretKey} secretValueHidden={secret?.secretValueHidden || false} - defaultValue={ - secret?.secretValueHidden - ? "" - : secret?.valueOverride || - secret?.value || - importedSecret?.secret?.value - } + defaultValue={getDefaultValue(secret, importedSecret)} secretId={secret?.id} isOverride={Boolean(secret?.valueOverride)} isImportedSecret={isImportedSecret} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx index 5c431427c..65d001e95 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx @@ -46,10 +46,9 @@ import { } from "@app/components/secrets/SecretReferenceDetails"; import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types"; -import { Blur } from "@app/components/v2/Blur"; import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faKey, faRotate } from "@fortawesome/free-solid-svg-icons"; +import { faEyeSlash, faKey, faRotate } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeSpriteName, formSchema, @@ -57,6 +56,8 @@ import { TFormSchema } from "./SecretListView.utils"; +const hiddenValue = "******"; + type Props = { secret: SecretV3RawSanitized; onSaveSecret: ( @@ -95,6 +96,23 @@ export const SecretItem = memo( const { permission } = useProjectPermission(); const { isRotatedSecret } = secret; + const canEditSecretValue = permission.can( + ProjectPermissionSecretActions.Edit, + subject(ProjectPermissionSub.Secrets, { + environment, + secretPath, + secretName: secret.key, + secretTags: ["*"] + }) + ); + + const getDefaultValue = () => { + if (secret.secretValueHidden) { + return canEditSecretValue ? hiddenValue : ""; + } + return secret.valueOverride || secret.value || ""; + }; + const { handleSubmit, control, @@ -108,11 +126,11 @@ export const SecretItem = memo( } = useForm({ defaultValues: { ...secret, - value: secret.secretValueHidden ? "" : secret.value + value: getDefaultValue() }, values: { ...secret, - value: secret.secretValueHidden ? "" : secret.value + value: getDefaultValue() }, resolver: zodResolver(formSchema) }); @@ -154,6 +172,7 @@ export const SecretItem = memo( secretTags: selectedTagSlugs }) ); + const { secretValueHidden } = secret; const [isSecValueCopied, setIsSecValueCopied] = useToggle(false); @@ -286,6 +305,13 @@ export const SecretItem = memo( tabIndex={0} role="button" > + {secretValueHidden && !isOverriden && ( + + + + )} {isOverriden ? ( )} /> - ) : secretValueHidden ? ( - ) : ( )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx index 5c70b91e4..1ef2c7076 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx @@ -49,8 +49,6 @@ export const SecretListView = ({ isProtectedBranch = false, importedBy }: Props) => { - console.log("secretssssss", secrets); - const queryClient = useQueryClient(); const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([ "deleteSecret",