mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 10:29:17 +00:00
merge from main
This commit is contained in:
@@ -188,7 +188,13 @@ export enum TableName {
|
||||
Relay = "relays",
|
||||
GatewayV2 = "gateways_v2",
|
||||
|
||||
KeyValueStore = "key_value_store"
|
||||
KeyValueStore = "key_value_store",
|
||||
|
||||
// PAM
|
||||
PamFolder = "pam_folders",
|
||||
PamResource = "pam_resources",
|
||||
PamAccount = "pam_accounts",
|
||||
PamSession = "pam_sessions"
|
||||
}
|
||||
|
||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
|
||||
|
||||
@@ -84,23 +84,6 @@ export const identityUaServiceFactory = ({
|
||||
|
||||
const LOCKOUT_KEY = `lockout:identity:${identityUa.identityId}:${IdentityAuthMethod.UNIVERSAL_AUTH}:${clientId}`;
|
||||
|
||||
let lock: Awaited<ReturnType<typeof keyStore.acquireLock>> | undefined;
|
||||
if (identityUa.lockoutEnabled) {
|
||||
try {
|
||||
lock = await keyStore.acquireLock([KeyStorePrefixes.IdentityLockoutLock(LOCKOUT_KEY)], 500, {
|
||||
retryCount: 3,
|
||||
retryDelay: 300,
|
||||
retryJitter: 100
|
||||
});
|
||||
} catch (e) {
|
||||
logger.info(
|
||||
`identity login failed to acquire lock [identityId=${identityUa.identityId}] [authMethod=${IdentityAuthMethod.UNIVERSAL_AUTH}]`
|
||||
);
|
||||
throw new RateLimitError({ message: "Failed to acquire lock: rate limit exceeded" });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const lockoutRaw = await keyStore.getItem(LOCKOUT_KEY);
|
||||
|
||||
let lockout: LockoutObject | undefined;
|
||||
@@ -140,13 +123,31 @@ export const identityUaServiceFactory = ({
|
||||
|
||||
if (!validClientSecretInfo) {
|
||||
if (identityUa.lockoutEnabled) {
|
||||
if (!lockout) {
|
||||
let lock: Awaited<ReturnType<typeof keyStore.acquireLock>> | undefined;
|
||||
try {
|
||||
lock = await keyStore.acquireLock([KeyStorePrefixes.IdentityLockoutLock(LOCKOUT_KEY)], 300, {
|
||||
retryCount: 3,
|
||||
retryDelay: 300,
|
||||
retryJitter: 100
|
||||
});
|
||||
|
||||
// Re-fetch the latest lockout data while holding the lock
|
||||
const lockoutRawNew = await keyStore.getItem(LOCKOUT_KEY);
|
||||
if (lockoutRawNew) {
|
||||
lockout = JSON.parse(lockoutRawNew) as LockoutObject;
|
||||
} else {
|
||||
lockout = {
|
||||
lockedOut: false,
|
||||
failedAttempts: 0
|
||||
};
|
||||
}
|
||||
|
||||
if (lockout.lockedOut) {
|
||||
throw new UnauthorizedError({
|
||||
message: "This identity auth method is temporarily locked, please try again later"
|
||||
});
|
||||
}
|
||||
|
||||
lockout.failedAttempts += 1;
|
||||
if (lockout.failedAttempts >= identityUa.lockoutThreshold) {
|
||||
lockout.lockedOut = true;
|
||||
@@ -157,10 +158,24 @@ export const identityUaServiceFactory = ({
|
||||
lockout.lockedOut ? identityUa.lockoutDurationSeconds : identityUa.lockoutCounterResetSeconds,
|
||||
JSON.stringify(lockout)
|
||||
);
|
||||
} catch (e) {
|
||||
if (lock === undefined) {
|
||||
logger.info(
|
||||
`identity login failed to acquire lock [identityId=${identityUa.identityId}] [authMethod=${IdentityAuthMethod.UNIVERSAL_AUTH}]`
|
||||
);
|
||||
throw new RateLimitError({ message: "Failed to acquire lock: rate limit exceeded" });
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
if (lock) {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnauthorizedError({ message: "Invalid credentials" });
|
||||
} else if (lockout) {
|
||||
// If credentials are valid, clear any existing lockout record
|
||||
await keyStore.deleteItem(LOCKOUT_KEY);
|
||||
}
|
||||
|
||||
@@ -258,9 +273,6 @@ export const identityUaServiceFactory = ({
|
||||
identityMembershipOrg,
|
||||
...accessTokenTTLParams
|
||||
};
|
||||
} finally {
|
||||
if (lock) await lock.release();
|
||||
}
|
||||
};
|
||||
|
||||
const attachUniversalAuth = async ({
|
||||
|
||||
@@ -40,14 +40,10 @@ To interact with various resources in Infisical, Machine Identities can authenti
|
||||
|
||||
## Identity Lockout
|
||||
|
||||
Lockout is a feature that prevents brute-force attacks on identity login endpoints. Auth methods that support lockout include: [Universal Auth](/documentation/platform/identities/universal-auth).
|
||||
Lockout is a feature that prevents brute-force attacks on identity login endpoints. Auth methods that support lockout include: [Universal Auth](/documentation/platform/identities/universal-auth), [LDAP Auth](/documentation/platform/identities/ldap-auth/general).
|
||||
|
||||
Supported auth methods have lockout enabled by default. If triggered, lockout temporarily disables the login endpoint for 5 minutes after 3 consecutive failed login attempts within a 30-second window. Lockout can be configured and disabled in the identity auth method settings.
|
||||
|
||||
<Warning>
|
||||
When Lockout is enabled, a rate limit of approximately 10 requests per second is enforced on relevant authentication endpoints. This security measure employs a protective lock to mitigate parallel login attacks. If this rate limitation interferes with your operational requirements, you may consider disabling Lockout.
|
||||
</Warning>
|
||||
|
||||
## FAQ
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
@@ -337,7 +337,7 @@ export const useMoveSecrets = ({
|
||||
destinationSecretPath,
|
||||
secretIds,
|
||||
shouldOverwrite,
|
||||
projectId
|
||||
projectSlug
|
||||
}) => {
|
||||
const { data } = await apiRequest.post<{
|
||||
isSourceUpdated: boolean;
|
||||
@@ -349,7 +349,7 @@ export const useMoveSecrets = ({
|
||||
destinationSecretPath,
|
||||
secretIds,
|
||||
shouldOverwrite,
|
||||
projectId
|
||||
projectSlug
|
||||
});
|
||||
|
||||
return data;
|
||||
|
||||
@@ -228,6 +228,7 @@ export type TDeleteSecretBatchDTO = {
|
||||
|
||||
export type TMoveSecretsDTO = {
|
||||
projectId: string;
|
||||
projectSlug: string;
|
||||
sourceEnvironment: string;
|
||||
sourceSecretPath: string;
|
||||
destinationEnvironment: string;
|
||||
|
||||
@@ -731,6 +731,9 @@ export const OverviewPage = () => {
|
||||
|
||||
userAvailableEnvs.forEach((env) => {
|
||||
secrets?.forEach((secret) => {
|
||||
// bulk actions don't apply to rotation secrets (move/delete)
|
||||
if (secret.isRotatedSecret) return;
|
||||
|
||||
if (allRowsSelectedOnPage.isChecked) {
|
||||
delete newChecks[EntryType.SECRET][secret.key];
|
||||
} else {
|
||||
|
||||
+2
@@ -66,6 +66,7 @@ const Content = ({
|
||||
secrets,
|
||||
environments,
|
||||
projectId,
|
||||
projectSlug,
|
||||
sourceSecretPath
|
||||
}: ContentProps) => {
|
||||
const [search, setSearch] = useState(sourceSecretPath);
|
||||
@@ -194,6 +195,7 @@ const Content = ({
|
||||
destinationEnvironment: environment.slug,
|
||||
destinationSecretPath: value.secretPath,
|
||||
projectId,
|
||||
projectSlug,
|
||||
secretIds: secretsToMove.map((sec) => sec.id)
|
||||
});
|
||||
|
||||
|
||||
@@ -570,6 +570,9 @@ const Page = () => {
|
||||
const newChecks = { ...selectedSecrets };
|
||||
|
||||
secrets?.forEach((secret) => {
|
||||
// bulk actions don't apply to rotation secrets (move/delete)
|
||||
if (secret.isRotatedSecret) return;
|
||||
|
||||
if (allRowsSelectedOnPage.isChecked) {
|
||||
delete newChecks[secret.id];
|
||||
} else {
|
||||
|
||||
+2
-4
@@ -72,10 +72,7 @@ import {
|
||||
useMoveSecrets,
|
||||
useUpdateSecretBatch
|
||||
} from "@app/hooks/api";
|
||||
import {
|
||||
dashboardKeys,
|
||||
fetchDashboardProjectSecretsByKeys
|
||||
} from "@app/hooks/api/dashboard/queries";
|
||||
import { dashboardKeys, fetchDashboardProjectSecretsByKeys } from "@app/hooks/api/dashboard/queries";
|
||||
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
|
||||
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
|
||||
import { PendingAction } from "@app/hooks/api/secretFolders/types";
|
||||
@@ -349,6 +346,7 @@ export const ActionBar = ({
|
||||
destinationEnvironment,
|
||||
destinationSecretPath,
|
||||
projectId,
|
||||
projectSlug: currentProject.slug,
|
||||
secretIds: secretsToMove.map((sec) => sec.id)
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user