mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
feat: update AWS IAM session duration handling and improve account access functionality
- Changed session duration parameter from maxSessionDuration to defaultSessionDuration for consistency. - Refactored AWS STS client creation to use a hardcoded default region, simplifying the configuration. - Enhanced PAM account access modal to include account path and project ID in the access request. - Updated various components and schemas to reflect the new session duration naming and improve type safety.
This commit is contained in:
@@ -591,7 +591,7 @@ export const pamAccountServiceFactory = ({
|
|||||||
targetRoleArn: awsCredentials.targetRoleArn,
|
targetRoleArn: awsCredentials.targetRoleArn,
|
||||||
roleSessionName: actorEmail,
|
roleSessionName: actorEmail,
|
||||||
projectId: account.projectId, // Use project ID as External ID for security
|
projectId: account.projectId, // Use project ID as External ID for security
|
||||||
sessionDuration: awsCredentials.maxSessionDuration
|
sessionDuration: awsCredentials.defaultSessionDuration
|
||||||
});
|
});
|
||||||
|
|
||||||
const session = await pamSessionDAL.create({
|
const session = await pamSessionDAL.create({
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ import { TAwsIamResourceConnectionDetails } from "./aws-iam-resource-types";
|
|||||||
|
|
||||||
const AWS_STS_MIN_DURATION_SECONDS = 900;
|
const AWS_STS_MIN_DURATION_SECONDS = 900;
|
||||||
|
|
||||||
const createStsClient = (region: string): STSClient => {
|
// We hardcode us-east-1 because:
|
||||||
|
// 1. IAM is global - roles can be assumed from any STS regional endpoint
|
||||||
|
// 2. The temporary credentials returned work globally across all AWS regions
|
||||||
|
// 3. The target account's resources can be in any region - it doesn't affect STS calls
|
||||||
|
const AWS_STS_DEFAULT_REGION = "us-east-1";
|
||||||
|
|
||||||
|
const createStsClient = (): STSClient => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const config: STSClientConfig = {
|
const config: STSClientConfig = {
|
||||||
region,
|
region: AWS_STS_DEFAULT_REGION,
|
||||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||||
sha256: CustomAWSHasher,
|
sha256: CustomAWSHasher,
|
||||||
credentials:
|
credentials:
|
||||||
@@ -31,7 +37,7 @@ export const validatePamRoleConnection = async (
|
|||||||
connectionDetails: TAwsIamResourceConnectionDetails,
|
connectionDetails: TAwsIamResourceConnectionDetails,
|
||||||
projectId: string
|
projectId: string
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
const stsClient = createStsClient(connectionDetails.region);
|
const stsClient = createStsClient();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await stsClient.send(
|
await stsClient.send(
|
||||||
@@ -58,7 +64,7 @@ export const validateTargetRoleAssumption = async ({
|
|||||||
targetRoleArn: string;
|
targetRoleArn: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}): Promise<boolean> => {
|
}): Promise<boolean> => {
|
||||||
const stsClient = createStsClient(connectionDetails.region);
|
const stsClient = createStsClient();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First assume the PAM role
|
// First assume the PAM role
|
||||||
@@ -77,7 +83,7 @@ export const validateTargetRoleAssumption = async ({
|
|||||||
|
|
||||||
// Then use the PAM role credentials to assume the target role
|
// Then use the PAM role credentials to assume the target role
|
||||||
const pamStsClient = new STSClient({
|
const pamStsClient = new STSClient({
|
||||||
region: connectionDetails.region,
|
region: AWS_STS_DEFAULT_REGION,
|
||||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||||
sha256: CustomAWSHasher,
|
sha256: CustomAWSHasher,
|
||||||
credentials: {
|
credentials: {
|
||||||
@@ -118,7 +124,7 @@ export const generateConsoleFederationUrl = async ({
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
sessionDuration: number;
|
sessionDuration: number;
|
||||||
}): Promise<{ consoleUrl: string; expiresAt: Date }> => {
|
}): Promise<{ consoleUrl: string; expiresAt: Date }> => {
|
||||||
const stsClient = createStsClient(connectionDetails.region);
|
const stsClient = createStsClient();
|
||||||
|
|
||||||
// First assume the PAM role
|
// First assume the PAM role
|
||||||
const pamRoleCredentials = await stsClient.send(
|
const pamRoleCredentials = await stsClient.send(
|
||||||
@@ -136,7 +142,7 @@ export const generateConsoleFederationUrl = async ({
|
|||||||
|
|
||||||
// Role chaining: use PAM role credentials to assume the target role
|
// Role chaining: use PAM role credentials to assume the target role
|
||||||
const pamStsClient = new STSClient({
|
const pamStsClient = new STSClient({
|
||||||
region: connectionDetails.region,
|
region: AWS_STS_DEFAULT_REGION,
|
||||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||||
sha256: CustomAWSHasher,
|
sha256: CustomAWSHasher,
|
||||||
credentials: {
|
credentials: {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const awsIamResourceFactory: TPamResourceFactory<TAwsIamResourceConnectio
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
{ roleArn: connectionDetails.roleArn, region: connectionDetails.region },
|
{ roleArn: connectionDetails.roleArn },
|
||||||
"[AWS IAM Resource Factory] PAM role connection validated successfully"
|
"[AWS IAM Resource Factory] PAM role connection validated successfully"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ const AWS_STS_MIN_SESSION_DURATION = 900; // 15 minutes
|
|||||||
const AWS_STS_MAX_SESSION_DURATION_ROLE_CHAINING = 3600; // 1 hour
|
const AWS_STS_MAX_SESSION_DURATION_ROLE_CHAINING = 3600; // 1 hour
|
||||||
|
|
||||||
export const AwsIamResourceConnectionDetailsSchema = z.object({
|
export const AwsIamResourceConnectionDetailsSchema = z.object({
|
||||||
region: z.string().trim().min(1),
|
|
||||||
roleArn: z.string().trim().min(1)
|
roleArn: z.string().trim().min(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AwsIamAccountCredentialsSchema = z.object({
|
export const AwsIamAccountCredentialsSchema = z.object({
|
||||||
targetRoleArn: z.string().trim().min(1).max(2048),
|
targetRoleArn: z.string().trim().min(1).max(2048),
|
||||||
maxSessionDuration: z.coerce
|
defaultSessionDuration: z.coerce
|
||||||
.number()
|
.number()
|
||||||
.min(AWS_STS_MIN_SESSION_DURATION)
|
.min(AWS_STS_MIN_SESSION_DURATION)
|
||||||
.max(AWS_STS_MAX_SESSION_DURATION_ROLE_CHAINING)
|
.max(AWS_STS_MAX_SESSION_DURATION_ROLE_CHAINING)
|
||||||
@@ -79,6 +78,6 @@ export const UpdateAwsIamAccountSchema = BaseUpdatePamAccountSchema.extend({
|
|||||||
export const SanitizedAwsIamAccountWithResourceSchema = BasePamAccountSchemaWithResource.extend({
|
export const SanitizedAwsIamAccountWithResourceSchema = BasePamAccountSchemaWithResource.extend({
|
||||||
credentials: AwsIamAccountCredentialsSchema.pick({
|
credentials: AwsIamAccountCredentialsSchema.pick({
|
||||||
targetRoleArn: true,
|
targetRoleArn: true,
|
||||||
maxSessionDuration: true
|
defaultSessionDuration: true
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ export const useDeletePamAccount = () => {
|
|||||||
|
|
||||||
export type TAccessPamAccountDTO = {
|
export type TAccessPamAccountDTO = {
|
||||||
accountId: string;
|
accountId: string;
|
||||||
|
accountPath: string;
|
||||||
|
projectId: string;
|
||||||
duration: string;
|
duration: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -141,11 +143,13 @@ export type TAccessPamAccountResponse = {
|
|||||||
|
|
||||||
export const useAccessPamAccount = () => {
|
export const useAccessPamAccount = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ accountId, duration }: TAccessPamAccountDTO) => {
|
mutationFn: async ({ accountId, accountPath, projectId, duration }: TAccessPamAccountDTO) => {
|
||||||
const { data } = await apiRequest.post<TAccessPamAccountResponse>(
|
const { data } = await apiRequest.post<TAccessPamAccountResponse>(
|
||||||
"/api/v1/pam/accounts/access",
|
"/api/v1/pam/accounts/access",
|
||||||
{
|
{
|
||||||
accountId,
|
accountId,
|
||||||
|
accountPath,
|
||||||
|
projectId,
|
||||||
duration
|
duration
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ import { TBasePamAccount } from "./base-account";
|
|||||||
import { TBasePamResource } from "./base-resource";
|
import { TBasePamResource } from "./base-resource";
|
||||||
|
|
||||||
export type TAwsIamConnectionDetails = {
|
export type TAwsIamConnectionDetails = {
|
||||||
region: string;
|
|
||||||
roleArn: string;
|
roleArn: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAwsIamCredentials = {
|
export type TAwsIamCredentials = {
|
||||||
targetRoleArn: string;
|
targetRoleArn: string;
|
||||||
maxSessionDuration: number;
|
defaultSessionDuration: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAwsIamResource = Omit<TBasePamResource, "gatewayId"> & {
|
export type TAwsIamResource = Omit<TBasePamResource, "gatewayId"> & {
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { faCopy } from "@fortawesome/free-regular-svg-icons";
|
import { faCopy } from "@fortawesome/free-regular-svg-icons";
|
||||||
import { faExternalLink, faUpRightFromSquare, faWarning } from "@fortawesome/free-solid-svg-icons";
|
import { faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import {
|
import { FormLabel, IconButton, Input, Modal, ModalContent } from "@app/components/v2";
|
||||||
Button,
|
import { PamResourceType, TPamAccount } from "@app/hooks/api/pam";
|
||||||
FormControl,
|
|
||||||
FormLabel,
|
|
||||||
IconButton,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalContent
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { PamResourceType, TPamAccount, useAccessPamAccount } from "@app/hooks/api/pam";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
account?: TPamAccount;
|
account?: TPamAccount;
|
||||||
@@ -24,124 +16,25 @@ type Props = {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AwsIamAccessContent = ({
|
export const PamAccessAccountModal = ({
|
||||||
account,
|
isOpen,
|
||||||
onOpenChange
|
|
||||||
}: {
|
|
||||||
account: TPamAccount;
|
|
||||||
onOpenChange: (isOpen: boolean) => void;
|
|
||||||
}) => {
|
|
||||||
const [durationInput, setDurationInput] = useState("1h");
|
|
||||||
const accessPamAccount = useAccessPamAccount();
|
|
||||||
|
|
||||||
const parsedDuration = useMemo(() => {
|
|
||||||
try {
|
|
||||||
const milliseconds = ms(durationInput);
|
|
||||||
if (!milliseconds) return null;
|
|
||||||
const seconds = Math.floor(milliseconds / 1000);
|
|
||||||
// Min 15 minutes (900s), max 1 hour (3600s) due to AWS role chaining limitation
|
|
||||||
if (seconds < 900 || seconds > 3600) return null;
|
|
||||||
return seconds;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}, [durationInput]);
|
|
||||||
|
|
||||||
const handleAccessConsole = async () => {
|
|
||||||
if (!parsedDuration) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await accessPamAccount.mutateAsync({
|
|
||||||
accountId: account.id,
|
|
||||||
duration: `${parsedDuration}s`
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.consoleUrl) {
|
|
||||||
// Open the AWS Console URL in a new tab
|
|
||||||
window.open(response.consoleUrl, "_blank", "noopener,noreferrer");
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: "AWS Console opened in new tab",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
|
|
||||||
onOpenChange(false);
|
|
||||||
} else {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to generate AWS Console URL",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to access AWS Console",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FormControl
|
|
||||||
label="Session Duration"
|
|
||||||
helperText="Min 15m, max 1h (AWS role chaining limit). Examples: 30m, 1h"
|
|
||||||
isError={durationInput.length > 0 && !parsedDuration}
|
|
||||||
errorText="Invalid duration. Use format like 15m, 30m, 1h"
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
value={durationInput}
|
|
||||||
onChange={(e) => setDurationInput(e.target.value)}
|
|
||||||
placeholder="1h"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<div className="mb-4 rounded-sm border border-yellow-600/30 bg-yellow-600/10 p-3">
|
|
||||||
<div className="flex items-start gap-2">
|
|
||||||
<FontAwesomeIcon icon={faWarning} className="mt-0.5 text-yellow-500" />
|
|
||||||
<div className="text-xs text-yellow-500">
|
|
||||||
<strong>Important:</strong> AWS Console sessions cannot be terminated early. The session
|
|
||||||
remains active until the STS token expires. All activity is logged in AWS CloudTrail.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={handleAccessConsole}
|
|
||||||
isLoading={accessPamAccount.isPending}
|
|
||||||
isDisabled={!parsedDuration}
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="w-full"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faExternalLink} />}
|
|
||||||
>
|
|
||||||
Open AWS Console
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CliAccessContent = ({
|
|
||||||
account,
|
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
|
account,
|
||||||
projectId,
|
projectId,
|
||||||
accountPath
|
accountPath
|
||||||
}: {
|
}: Props) => {
|
||||||
account: TPamAccount;
|
const [duration, setDuration] = useState("4h");
|
||||||
onOpenChange: (isOpen: boolean) => void;
|
|
||||||
projectId: string;
|
|
||||||
accountPath?: string;
|
|
||||||
}) => {
|
|
||||||
let fullAccountPath = account?.name;
|
|
||||||
if (accountPath) {
|
|
||||||
let path = accountPath;
|
|
||||||
if (path.startsWith("/")) path = path.slice(1);
|
|
||||||
fullAccountPath = `${path}/${account?.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { protocol, hostname, port } = window.location;
|
const { protocol, hostname, port } = window.location;
|
||||||
const portSuffix = port && port !== "80" && port !== "443" ? `:${port}` : "";
|
const portSuffix = port && port !== "80" && port !== "443" ? `:${port}` : "";
|
||||||
const siteURL = `${protocol}//${hostname}${portSuffix}`;
|
const siteURL = `${protocol}//${hostname}${portSuffix}`;
|
||||||
|
|
||||||
const [duration, setDuration] = useState("4h");
|
let fullAccountPath = account?.name ?? "";
|
||||||
|
if (accountPath) {
|
||||||
|
let path = accountPath;
|
||||||
|
if (path.startsWith("/")) path = path.slice(1);
|
||||||
|
fullAccountPath = `${path}/${account?.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
const isDurationValid = useMemo(() => duration && ms(duration || "1s") > 0, [duration]);
|
const isDurationValid = useMemo(() => duration && ms(duration || "1s") > 0, [duration]);
|
||||||
|
|
||||||
@@ -196,87 +89,58 @@ const CliAccessContent = ({
|
|||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}, [account, cliDuration]);
|
}, [account, fullAccountPath, projectId, cliDuration, siteURL]);
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FormLabel
|
|
||||||
label="Duration"
|
|
||||||
tooltipText="The maximum duration of your session. Ex: 1h, 3w, 30d"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
value={duration}
|
|
||||||
onChange={(e) => setDuration(e.target.value)}
|
|
||||||
placeholder="permanent"
|
|
||||||
isError={!isDurationValid}
|
|
||||||
/>
|
|
||||||
<FormLabel label="CLI Command" className="mt-4" />
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Input value={command} isDisabled />
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="copy"
|
|
||||||
variant="outline_bg"
|
|
||||||
colorSchema="secondary"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(command);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: "Command copied to clipboard",
|
|
||||||
type: "info"
|
|
||||||
});
|
|
||||||
|
|
||||||
onOpenChange(false);
|
|
||||||
}}
|
|
||||||
className="w-10"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faCopy} />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
<a
|
|
||||||
href="https://infisical.com/docs/cli/overview"
|
|
||||||
target="_blank"
|
|
||||||
className="mt-2 flex h-4 w-fit items-center gap-2 border-b border-mineshaft-400 text-sm text-mineshaft-400 transition-colors duration-100 hover:border-yellow-400 hover:text-yellow-400"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<span>Install the Infisical CLI</span>
|
|
||||||
<FontAwesomeIcon icon={faUpRightFromSquare} className="size-3" />
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PamAccessAccountModal = ({
|
|
||||||
isOpen,
|
|
||||||
onOpenChange,
|
|
||||||
account,
|
|
||||||
projectId,
|
|
||||||
accountPath
|
|
||||||
}: Props) => {
|
|
||||||
if (!account) return null;
|
if (!account) return null;
|
||||||
|
|
||||||
const isAwsIam = account.resource.resourceType === PamResourceType.AwsIam;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
className="max-w-2xl pb-2"
|
className="max-w-2xl pb-2"
|
||||||
title="Access Account"
|
title="Access Account"
|
||||||
subTitle={
|
subTitle={`Access ${account.name} using a CLI command.`}
|
||||||
isAwsIam
|
|
||||||
? `Access ${account.name} via AWS Console.`
|
|
||||||
: `Access ${account.name} using a CLI command.`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{isAwsIam ? (
|
<FormLabel
|
||||||
<AwsIamAccessContent account={account} onOpenChange={onOpenChange} />
|
label="Duration"
|
||||||
) : (
|
tooltipText="The maximum duration of your session. Ex: 1h, 3w, 30d"
|
||||||
<CliAccessContent
|
/>
|
||||||
account={account}
|
<Input
|
||||||
onOpenChange={onOpenChange}
|
value={duration}
|
||||||
projectId={projectId}
|
onChange={(e) => setDuration(e.target.value)}
|
||||||
accountPath={accountPath}
|
placeholder="permanent"
|
||||||
/>
|
isError={!isDurationValid}
|
||||||
)}
|
/>
|
||||||
|
<FormLabel label="CLI Command" className="mt-4" />
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input value={command} isDisabled />
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy"
|
||||||
|
variant="outline_bg"
|
||||||
|
colorSchema="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(command);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Command copied to clipboard",
|
||||||
|
type: "info"
|
||||||
|
});
|
||||||
|
|
||||||
|
onOpenChange(false);
|
||||||
|
}}
|
||||||
|
className="w-10"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faCopy} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/cli/overview"
|
||||||
|
target="_blank"
|
||||||
|
className="mt-2 flex h-4 w-fit items-center gap-2 border-b border-mineshaft-400 text-sm text-mineshaft-400 transition-colors duration-100 hover:border-yellow-400 hover:text-yellow-400"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<span>Install the Infisical CLI</span>
|
||||||
|
<FontAwesomeIcon icon={faUpRightFromSquare} className="size-3" />
|
||||||
|
</a>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
+49
-45
@@ -1,4 +1,6 @@
|
|||||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -12,6 +14,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
ModalClose
|
ModalClose
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
|
import { CopyButton } from "@app/components/v2/CopyButton";
|
||||||
import { useProject } from "@app/context";
|
import { useProject } from "@app/context";
|
||||||
import { PamResourceType, TAwsIamAccount } from "@app/hooks/api/pam";
|
import { PamResourceType, TAwsIamAccount } from "@app/hooks/api/pam";
|
||||||
|
|
||||||
@@ -35,7 +38,7 @@ const AwsIamCredentialsSchema = z.object({
|
|||||||
message: "ARN must be in the format 'arn:aws:iam::123456789012:role/RoleName'"
|
message: "ARN must be in the format 'arn:aws:iam::123456789012:role/RoleName'"
|
||||||
}),
|
}),
|
||||||
// Max 1 hour (3600s) due to AWS role chaining limitation, min 15 min (900s)
|
// Max 1 hour (3600s) due to AWS role chaining limitation, min 15 min (900s)
|
||||||
maxSessionDuration: z.coerce
|
defaultSessionDuration: z.coerce
|
||||||
.number()
|
.number()
|
||||||
.min(900, "Minimum session duration is 900 seconds (15 minutes)")
|
.min(900, "Minimum session duration is 900 seconds (15 minutes)")
|
||||||
.max(3600, "Maximum session duration is 3600 seconds (1 hour)")
|
.max(3600, "Maximum session duration is 3600 seconds (1 hour)")
|
||||||
@@ -57,6 +60,22 @@ export const AwsIamAccountForm = ({ account, onSubmit }: Props) => {
|
|||||||
const isUpdate = Boolean(account);
|
const isUpdate = Boolean(account);
|
||||||
const { projectId } = useProject();
|
const { projectId } = useProject();
|
||||||
|
|
||||||
|
const targetRoleTrustPolicy = `{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/<YOUR_PAM_ROLE_NAME>"
|
||||||
|
},
|
||||||
|
"Action": "sts:AssumeRole",
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"sts:ExternalId": "${projectId}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}`;
|
||||||
|
|
||||||
const form = useForm<FormData>({
|
const form = useForm<FormData>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: account ?? {
|
defaultValues: account ?? {
|
||||||
@@ -64,7 +83,7 @@ export const AwsIamAccountForm = ({ account, onSubmit }: Props) => {
|
|||||||
description: "",
|
description: "",
|
||||||
credentials: {
|
credentials: {
|
||||||
targetRoleArn: "",
|
targetRoleArn: "",
|
||||||
maxSessionDuration: 3600
|
defaultSessionDuration: 3600
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -104,7 +123,7 @@ export const AwsIamAccountForm = ({ account, onSubmit }: Props) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="credentials.maxSessionDuration"
|
name="credentials.defaultSessionDuration"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
@@ -112,7 +131,7 @@ export const AwsIamAccountForm = ({ account, onSubmit }: Props) => {
|
|||||||
helperText="In seconds. Min 900 (15m), max 3600 (1h) due to AWS role chaining limit."
|
helperText="In seconds. Min 900 (15m), max 3600 (1h) due to AWS role chaining limit."
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
label="Session Duration (seconds)"
|
label="Default Session Duration (seconds)"
|
||||||
>
|
>
|
||||||
<Input {...field} type="number" placeholder="3600" />
|
<Input {...field} type="number" placeholder="3600" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -120,10 +139,19 @@ export const AwsIamAccountForm = ({ account, onSubmit }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Accordion type="single" collapsible className="mb-4 w-full bg-mineshaft-700">
|
<Accordion
|
||||||
<AccordionItem value="target-role-setup">
|
type="single"
|
||||||
<AccordionTrigger>Target Role Setup</AccordionTrigger>
|
collapsible
|
||||||
<AccordionContent>
|
className="mb-4 w-full rounded-r border-l-2 border-l-primary bg-mineshaft-300/5"
|
||||||
|
>
|
||||||
|
<AccordionItem value="target-role-setup" className="border-b-0">
|
||||||
|
<AccordionTrigger className="px-4 py-2.5 hover:no-underline [&[data-state=open]]:pb-1">
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
|
||||||
|
Target Role Setup
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="px-4 pb-2.5">
|
||||||
<p className="mb-3 text-sm text-mineshaft-300">
|
<p className="mb-3 text-sm text-mineshaft-300">
|
||||||
The target role must have a trust policy that allows the Infisical PAM role to
|
The target role must have a trust policy that allows the Infisical PAM role to
|
||||||
assume it. If you used the{" "}
|
assume it. If you used the{" "}
|
||||||
@@ -134,32 +162,24 @@ export const AwsIamAccountForm = ({ account, onSubmit }: Props) => {
|
|||||||
<p className="mb-2 text-sm font-medium text-mineshaft-200">
|
<p className="mb-2 text-sm font-medium text-mineshaft-200">
|
||||||
Target role trust policy:
|
Target role trust policy:
|
||||||
</p>
|
</p>
|
||||||
<pre className="mb-3 max-h-45 overflow-y-auto rounded-sm border border-mineshaft-600 bg-mineshaft-800 p-2 text-xs whitespace-pre-wrap text-mineshaft-300">
|
<div className="relative mb-3">
|
||||||
{`{
|
<div className="absolute top-1 right-3">
|
||||||
"Version": "2012-10-17",
|
<CopyButton value={targetRoleTrustPolicy} size="sm" variant="plain" />
|
||||||
"Statement": [{
|
</div>
|
||||||
"Effect": "Allow",
|
<pre className="max-h-45 overflow-y-auto rounded-sm border border-mineshaft-600 bg-mineshaft-800 p-2 pr-8 text-xs whitespace-pre-wrap text-mineshaft-300">
|
||||||
"Principal": {
|
{targetRoleTrustPolicy}
|
||||||
"AWS": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/<YOUR_PAM_ROLE_NAME>"
|
</pre>
|
||||||
},
|
</div>
|
||||||
"Action": "sts:AssumeRole",
|
|
||||||
"Condition": {
|
|
||||||
"StringEquals": {
|
|
||||||
"sts:ExternalId": "${projectId}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}`}
|
|
||||||
</pre>
|
|
||||||
<p className="text-xs text-mineshaft-400">
|
<p className="text-xs text-mineshaft-400">
|
||||||
<strong>Note:</strong> Replace{" "}
|
<strong>Note:</strong> Replace{" "}
|
||||||
<code className="rounded bg-mineshaft-700 px-1"><YOUR_ACCOUNT_ID></code> with
|
<code className="rounded bg-mineshaft-700 px-1"><YOUR_ACCOUNT_ID></code> with
|
||||||
your AWS account ID and{" "}
|
your AWS account ID and{" "}
|
||||||
<code className="rounded bg-mineshaft-700 px-1"><YOUR_PAM_ROLE_NAME></code>{" "}
|
<code className="rounded bg-mineshaft-700 px-1"><YOUR_PAM_ROLE_NAME></code>{" "}
|
||||||
with the name of the PAM role you created (e.g.,{" "}
|
with the name of the PAM role you created and used in the "Resources" tab
|
||||||
<code className="rounded bg-mineshaft-700 px-1">InfisicalPAMRole</code>). The
|
(e.g., <code className="rounded bg-mineshaft-700 px-1">InfisicalPAMRole</code>). The
|
||||||
External ID <code className="rounded bg-mineshaft-700 px-1">{projectId}</code> is
|
External ID{" "}
|
||||||
your current project ID. If your target role name doesn't follow the{" "}
|
<code className="rounded bg-mineshaft-700 px-1 font-bold">{projectId}</code> is your
|
||||||
|
current project ID. If your target role name doesn't follow the{" "}
|
||||||
<code className="rounded bg-mineshaft-700 px-1">infisical-pam-*</code> pattern, you
|
<code className="rounded bg-mineshaft-700 px-1">infisical-pam-*</code> pattern, you
|
||||||
must update the PAM role's permissions policy to include the target role ARN.
|
must update the PAM role's permissions policy to include the target role ARN.
|
||||||
</p>
|
</p>
|
||||||
@@ -167,22 +187,6 @@ export const AwsIamAccountForm = ({ account, onSubmit }: Props) => {
|
|||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<div className="rounded-sm border border-yellow-600/30 bg-yellow-600/10 p-3">
|
|
||||||
<p className="text-xs text-yellow-500">
|
|
||||||
<strong>Note:</strong> While users cannot terminate AWS Console sessions directly,
|
|
||||||
administrators can revoke active sessions by using the{" "}
|
|
||||||
<a
|
|
||||||
href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_revoke-sessions.html"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline hover:text-yellow-400"
|
|
||||||
>
|
|
||||||
Revoke Sessions
|
|
||||||
</a>{" "}
|
|
||||||
feature in the IAM console. All activity is logged in AWS CloudTrail.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 flex items-center">
|
<div className="mt-6 flex items-center">
|
||||||
<Button
|
<Button
|
||||||
className="mr-4"
|
className="mr-4"
|
||||||
|
|||||||
@@ -427,7 +427,15 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
onAccess={(e: TPamAccount) => {
|
onAccess={(e: TPamAccount) => {
|
||||||
// For AWS IAM, directly open console without modal
|
// For AWS IAM, directly open console without modal
|
||||||
if (e.resource.resourceType === PamResourceType.AwsIam) {
|
if (e.resource.resourceType === PamResourceType.AwsIam) {
|
||||||
accessAwsIam(e);
|
let fullAccountPath = e?.name;
|
||||||
|
const folderPath = e.folderId ? folderPaths[e.folderId] : undefined;
|
||||||
|
if (folderPath) {
|
||||||
|
let path = folderPath;
|
||||||
|
if (path.startsWith("/")) path = path.slice(1);
|
||||||
|
fullAccountPath = `${path}/${e?.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
accessAwsIam(e, fullAccountPath);
|
||||||
} else {
|
} else {
|
||||||
handlePopUpOpen("accessAccount", e);
|
handlePopUpOpen("accessAccount", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const useAccessAwsIamAccount = () => {
|
|||||||
const accessPamAccount = useAccessPamAccount();
|
const accessPamAccount = useAccessPamAccount();
|
||||||
const [loadingAccountId, setLoadingAccountId] = useState<string | null>(null);
|
const [loadingAccountId, setLoadingAccountId] = useState<string | null>(null);
|
||||||
|
|
||||||
const accessAwsIam = async (account: TPamAccount) => {
|
const accessAwsIam = async (account: TPamAccount, accountPath: string) => {
|
||||||
if (account.resource.resourceType !== PamResourceType.AwsIam) {
|
if (account.resource.resourceType !== PamResourceType.AwsIam) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,9 @@ export const useAccessAwsIamAccount = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await accessPamAccount.mutateAsync({
|
const response = await accessPamAccount.mutateAsync({
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
duration: `${(account.credentials as TAwsIamCredentials).maxSessionDuration}s`
|
accountPath,
|
||||||
|
projectId: account.projectId,
|
||||||
|
duration: `${(account.credentials as TAwsIamCredentials).defaultSessionDuration}s`
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.consoleUrl) {
|
if (response.consoleUrl) {
|
||||||
|
|||||||
+65
-52
@@ -1,4 +1,6 @@
|
|||||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -12,6 +14,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
ModalClose
|
ModalClose
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
|
import { CopyButton } from "@app/components/v2/CopyButton";
|
||||||
import { useProject } from "@app/context";
|
import { useProject } from "@app/context";
|
||||||
import { PamResourceType, TAwsIamResource } from "@app/hooks/api/pam";
|
import { PamResourceType, TAwsIamResource } from "@app/hooks/api/pam";
|
||||||
import { slugSchema } from "@app/lib/schemas";
|
import { slugSchema } from "@app/lib/schemas";
|
||||||
@@ -24,7 +27,6 @@ type Props = {
|
|||||||
const arnRoleRegex = /^arn:aws:iam::\d{12}:role\/[\w+=,.@/-]+$/;
|
const arnRoleRegex = /^arn:aws:iam::\d{12}:role\/[\w+=,.@/-]+$/;
|
||||||
|
|
||||||
const AwsIamConnectionDetailsSchema = z.object({
|
const AwsIamConnectionDetailsSchema = z.object({
|
||||||
region: z.string().trim().min(1, "Region is required"),
|
|
||||||
roleArn: z
|
roleArn: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -50,12 +52,36 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
const isUpdate = Boolean(resource);
|
const isUpdate = Boolean(resource);
|
||||||
const { projectId } = useProject();
|
const { projectId } = useProject();
|
||||||
|
|
||||||
|
const permissionsPolicy = `{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "sts:AssumeRole",
|
||||||
|
"Resource": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/infisical-pam-*"
|
||||||
|
}]
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const trustPolicy = `{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": "arn:aws:iam::${INFISICAL_AWS_ACCOUNT_US}:root"
|
||||||
|
},
|
||||||
|
"Action": "sts:AssumeRole",
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"sts:ExternalId": "${projectId}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}`;
|
||||||
|
|
||||||
const form = useForm<FormData>({
|
const form = useForm<FormData>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: resource ?? {
|
defaultValues: resource ?? {
|
||||||
resourceType: PamResourceType.AwsIam,
|
resourceType: PamResourceType.AwsIam,
|
||||||
connectionDetails: {
|
connectionDetails: {
|
||||||
region: "",
|
|
||||||
roleArn: ""
|
roleArn: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,21 +111,6 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="connectionDetails.region"
|
|
||||||
control={control}
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
isError={Boolean(error?.message)}
|
|
||||||
errorText={error?.message}
|
|
||||||
label="AWS Region"
|
|
||||||
tooltipText="This region is used for the STS endpoint and initial console URL. It does not restrict access to resources in other regions. To restrict region access, configure region conditions in the target role's IAM policy."
|
|
||||||
>
|
|
||||||
<Input placeholder="us-east-1" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="connectionDetails.roleArn"
|
name="connectionDetails.roleArn"
|
||||||
control={control}
|
control={control}
|
||||||
@@ -115,10 +126,19 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Accordion type="single" collapsible className="mt-4 w-full bg-mineshaft-700">
|
<Accordion
|
||||||
<AccordionItem value="aws-iam-role-setup">
|
type="single"
|
||||||
<AccordionTrigger>AWS IAM Role Setup</AccordionTrigger>
|
collapsible
|
||||||
<AccordionContent>
|
className="mt-4 w-full rounded-r border-l-2 border-l-primary bg-mineshaft-300/5"
|
||||||
|
>
|
||||||
|
<AccordionItem value="aws-iam-role-setup" className="border-b-0">
|
||||||
|
<AccordionTrigger className="px-4 py-2.5 hover:no-underline [&[data-state=open]]:pb-1">
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
|
||||||
|
AWS IAM Role Setup
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="px-4 pb-2.5">
|
||||||
<p className="mb-3 text-sm text-mineshaft-300">
|
<p className="mb-3 text-sm text-mineshaft-300">
|
||||||
Before creating this resource, you need to set up an IAM role in your AWS account
|
Before creating this resource, you need to set up an IAM role in your AWS account
|
||||||
that Infisical can assume. Follow these steps:
|
that Infisical can assume. Follow these steps:
|
||||||
@@ -132,16 +152,14 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
<code className="rounded bg-mineshaft-700 px-1 text-xs">infisical-pam-*</code>{" "}
|
<code className="rounded bg-mineshaft-700 px-1 text-xs">infisical-pam-*</code>{" "}
|
||||||
naming convention for target roles.
|
naming convention for target roles.
|
||||||
</p>
|
</p>
|
||||||
<pre className="mb-4 max-h-40 overflow-y-auto rounded-sm border border-mineshaft-600 bg-mineshaft-800 p-2 text-xs whitespace-pre-wrap text-mineshaft-300">
|
<div className="relative mb-4">
|
||||||
{`{
|
<div className="absolute top-1 right-1">
|
||||||
"Version": "2012-10-17",
|
<CopyButton value={permissionsPolicy} size="sm" variant="plain" />
|
||||||
"Statement": [{
|
</div>
|
||||||
"Effect": "Allow",
|
<pre className="max-h-45 overflow-y-auto rounded-sm border border-mineshaft-600 bg-mineshaft-800 p-2 pr-8 text-xs whitespace-pre-wrap text-mineshaft-300">
|
||||||
"Action": "sts:AssumeRole",
|
{permissionsPolicy}
|
||||||
"Resource": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/infisical-pam-*"
|
</pre>
|
||||||
}]
|
</div>
|
||||||
}`}
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<p className="mb-2 text-sm font-medium text-mineshaft-200">
|
<p className="mb-2 text-sm font-medium text-mineshaft-200">
|
||||||
Step 2: Create the PAM role with a trust policy
|
Step 2: Create the PAM role with a trust policy
|
||||||
@@ -151,31 +169,26 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
<code className="rounded bg-mineshaft-700 px-1 text-xs">InfisicalPAMRole</code>)
|
<code className="rounded bg-mineshaft-700 px-1 text-xs">InfisicalPAMRole</code>)
|
||||||
with the permissions policy above and the following trust policy:
|
with the permissions policy above and the following trust policy:
|
||||||
</p>
|
</p>
|
||||||
<pre className="mb-4 max-h-40 overflow-y-auto rounded-sm border border-mineshaft-600 bg-mineshaft-800 p-2 text-xs whitespace-pre-wrap text-mineshaft-300">
|
<div className="relative mb-4">
|
||||||
{`{
|
<div className="absolute top-1 right-3">
|
||||||
"Version": "2012-10-17",
|
<CopyButton value={trustPolicy} size="sm" variant="plain" />
|
||||||
"Statement": [{
|
</div>
|
||||||
"Effect": "Allow",
|
<pre className="max-h-40 overflow-y-auto rounded-sm border border-mineshaft-600 bg-mineshaft-800 p-2 pr-8 text-xs whitespace-pre-wrap text-mineshaft-300">
|
||||||
"Principal": {
|
{trustPolicy}
|
||||||
"AWS": "arn:aws:iam::${INFISICAL_AWS_ACCOUNT_US}:root"
|
</pre>
|
||||||
},
|
</div>
|
||||||
"Action": "sts:AssumeRole",
|
|
||||||
"Condition": {
|
|
||||||
"StringEquals": {
|
|
||||||
"sts:ExternalId": "${projectId}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}`}
|
|
||||||
</pre>
|
|
||||||
<p className="text-xs text-mineshaft-400">
|
<p className="text-xs text-mineshaft-400">
|
||||||
<strong>Note:</strong> Use{" "}
|
<strong>Note:</strong> Use{" "}
|
||||||
<code className="rounded bg-mineshaft-700 px-1">{INFISICAL_AWS_ACCOUNT_US}</code>{" "}
|
<code className="rounded bg-mineshaft-700 px-1 font-bold">
|
||||||
|
{INFISICAL_AWS_ACCOUNT_US}
|
||||||
|
</code>{" "}
|
||||||
for US region or{" "}
|
for US region or{" "}
|
||||||
<code className="rounded bg-mineshaft-700 px-1">{INFISICAL_AWS_ACCOUNT_EU}</code>{" "}
|
<code className="rounded bg-mineshaft-700 px-1 font-bold">
|
||||||
|
{INFISICAL_AWS_ACCOUNT_EU}
|
||||||
|
</code>{" "}
|
||||||
for EU region. The External ID{" "}
|
for EU region. The External ID{" "}
|
||||||
<code className="rounded bg-mineshaft-700 px-1">{projectId}</code> is your current
|
<code className="rounded bg-mineshaft-700 px-1 font-bold">{projectId}</code> is your
|
||||||
project ID.
|
current project ID.
|
||||||
</p>
|
</p>
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|||||||
Reference in New Issue
Block a user