mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge branch 'main' into feat/teamcity-ignore-inherited-secrets
This commit is contained in:
@@ -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<R extends object = object> = Partial<R> & {
|
||||
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
||||
$complex?: TKnexDynamicOperator<R>;
|
||||
};
|
||||
|
||||
export const buildFindFilter =
|
||||
<R extends object = object>({ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>) =>
|
||||
<R extends object = object>(
|
||||
{ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>,
|
||||
tableName?: TableName,
|
||||
excludeKeys?: Array<keyof R>
|
||||
) =>
|
||||
(bd: Knex.QueryBuilder<R, R>) => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
const findOne = async (filter: Partial<TSecretsV2>, 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`,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
: {
|
||||
|
||||
@@ -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
|
||||
|
||||
<Steps>
|
||||
<Step title="Go to Project Access">
|
||||
Click on the user or identity you want to assume.
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Click Assume Privilege">
|
||||
Click **Assume Privilege**, then type `assume` to confirm and start your session.
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Session is Active">
|
||||
You will see a yellow banner indicating that your assume privilege session is active. You can exit at any time by clicking **Exit**.
|
||||
|
||||

|
||||
</Step>
|
||||
</Steps>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 335 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 360 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 439 KiB |
@@ -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",
|
||||
@@ -887,8 +888,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",
|
||||
|
||||
@@ -93,6 +93,7 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
onFocus={(evt) => {
|
||||
onFocus?.(evt);
|
||||
setIsSecretFocused.on();
|
||||
evt.currentTarget.select();
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
spellCheck={false}
|
||||
|
||||
@@ -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 && (
|
||||
<Tooltip
|
||||
content={`You do not have access to view the current value${canEditSecretValue && !isRotatedSecret ? ", but you can set a new one" : "."}`}
|
||||
>
|
||||
<FontAwesomeIcon className="pl-2" size="sm" icon={faEyeSlash} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className="flex-grow border-r border-r-mineshaft-600 pl-1 pr-2">
|
||||
{secretValueHidden ? (
|
||||
<Blur tooltipText="You do not have permission to read the value of this secret." />
|
||||
) : (
|
||||
<Controller
|
||||
disabled={isImportedSecret && !defaultValue}
|
||||
control={control}
|
||||
name="value"
|
||||
render={({ field }) => (
|
||||
<InfisicalSecretInput
|
||||
{...field}
|
||||
isReadOnly={isImportedSecret || isRotatedSecret}
|
||||
value={field.value as string}
|
||||
key="secret-input"
|
||||
isVisible={isVisible}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
isImport={isImportedSecret}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Controller
|
||||
disabled={isImportedSecret && !defaultValue}
|
||||
control={control}
|
||||
name="value"
|
||||
render={({ field }) => (
|
||||
<InfisicalSecretInput
|
||||
{...field}
|
||||
isReadOnly={isImportedSecret || (isRotatedSecret && !isOverride)}
|
||||
value={field.value as string}
|
||||
key="secret-input"
|
||||
isVisible={isVisible && !secretValueHidden}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
isImport={isImportedSecret}
|
||||
defaultValue={secretValueHidden ? "" : undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { subject } from "@casl/ability";
|
||||
import { faCircle } from "@fortawesome/free-regular-svg-icons";
|
||||
import {
|
||||
faAngleDown,
|
||||
@@ -14,6 +15,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Button, Checkbox, TableContainer, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { useProjectPermission } from "@app/context";
|
||||
import {
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/context/ProjectPermissionContext/types";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
||||
import { WorkspaceEnv } from "@app/hooks/api/types";
|
||||
@@ -64,6 +70,28 @@ export const SecretOverviewTableRow = ({
|
||||
const totalCols = environments.length + 1; // secret key row
|
||||
const [isSecretVisible, setIsSecretVisible] = useToggle();
|
||||
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
const getDefaultValue = (
|
||||
secret: SecretV3RawSanitized | undefined,
|
||||
importedSecret: { secret?: SecretV3RawSanitized } | undefined
|
||||
) => {
|
||||
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 (
|
||||
<>
|
||||
<Tr isHoverable isSelectable onClick={() => 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}
|
||||
|
||||
@@ -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<TFormSchema>({
|
||||
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 && (
|
||||
<Tooltip
|
||||
content={`You do not have access to view the current value${canEditSecretValue && !isRotatedSecret ? ", but you can set a new one" : "."}`}
|
||||
>
|
||||
<FontAwesomeIcon className="pr-2" size="sm" icon={faEyeSlash} />
|
||||
</Tooltip>
|
||||
)}
|
||||
{isOverriden ? (
|
||||
<Controller
|
||||
name="valueOverride"
|
||||
@@ -301,8 +327,6 @@ export const SecretItem = memo(
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : secretValueHidden ? (
|
||||
<Blur tooltipText="You do not have permission to read the value of this secret." />
|
||||
) : (
|
||||
<Controller
|
||||
name="value"
|
||||
@@ -312,11 +336,11 @@ export const SecretItem = memo(
|
||||
<InfisicalSecretInput
|
||||
isReadOnly={isReadOnly || isRotatedSecret}
|
||||
key="secret-value"
|
||||
isVisible={isVisible}
|
||||
isVisible={isVisible && !secretValueHidden}
|
||||
environment={environment}
|
||||
secretPath={secretPath}
|
||||
{...field}
|
||||
defaultValue={secretValueHidden ? "" : undefined}
|
||||
defaultValue={secretValueHidden ? hiddenValue : undefined}
|
||||
containerClassName="py-1.5 rounded-md transition-all"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user