diff --git a/docs/docs.json b/docs/docs.json index 39ebf31a9..91fedf0bb 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -795,6 +795,12 @@ "documentation/platform/pam/product-reference/session-recording", "documentation/platform/pam/product-reference/credential-rotation" ] + }, + { + "group": "Resources", + "pages": [ + "documentation/platform/pam/resources/aws-iam" + ] } ] } diff --git a/docs/documentation/platform/pam/resources/aws-iam.mdx b/docs/documentation/platform/pam/resources/aws-iam.mdx new file mode 100644 index 000000000..2c1352c49 --- /dev/null +++ b/docs/documentation/platform/pam/resources/aws-iam.mdx @@ -0,0 +1,244 @@ +--- +title: "AWS IAM" +sidebarTitle: "AWS IAM" +description: "Learn how to configure AWS Management Console access through Infisical PAM for secure, audited, and just-in-time access to AWS." +--- + +Infisical PAM supports secure, just-in-time access to the **AWS Management Console** through federated sign-in. This allows your team to access AWS without sharing long-lived credentials, while maintaining a complete audit trail of who accessed what and when. + +## How It Works + +Unlike database or SSH resources that require a Gateway for network connectivity, AWS Console access works differently. Infisical uses AWS STS (Security Token Service) to assume roles on your behalf and generates temporary federated sign-in URLs. + +```mermaid +sequenceDiagram + participant User + participant Infisical + participant PAM Role as PAM Role
(Your AWS Account) + participant Target Role as Target Role
(Your AWS Account) + participant Console as AWS Console + + User->>Infisical: Request AWS Console access + Infisical->>PAM Role: AssumeRole (with ExternalId) + PAM Role-->>Infisical: Temporary credentials + Infisical->>Target Role: AssumeRole (role chaining) + Target Role-->>Infisical: Session credentials + Infisical->>Console: Generate federation URL + Console-->>Infisical: Signed console URL + Infisical-->>User: Return console URL + User->>Console: Open AWS Console (federated) +``` + +### Key Concepts + +1. **PAM Role**: An IAM role in your AWS account that trusts Infisical. This is the "bridge" role that Infisical assumes first. + +2. **Target Role**: The IAM role that end users will actually use in the AWS Console. The PAM Role assumes this role on behalf of the user. + +3. **Role Chaining**: Infisical uses AWS role chaining - it first assumes the PAM Role, then uses those credentials to assume the Target Role. This provides an additional layer of security and audit capability. + +4. **External ID**: A unique identifier (your Infisical Project ID) used in the trust policy to prevent [confused deputy attacks](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html). + +## Prerequisites + +Before configuring AWS Console access in Infisical PAM, you need to set up two IAM roles in your AWS account: + +1. **PAM Role** - Trusted by Infisical, can assume target roles +2. **Target Role(s)** - The actual roles users will use in the console + + + **No Gateway Required**: Unlike database or SSH resources, AWS Console access does not require an Infisical Gateway. Infisical communicates directly with AWS APIs. + + +## Step 1: Create the PAM Resource + +The PAM Resource represents the connection between Infisical and your AWS account. It contains the PAM Role that Infisical will assume. + +### 1.1 Create the PAM Role Permissions Policy + +First, create an IAM policy that allows the PAM Role to assume your target roles. We recommend using a wildcard pattern so you can add new target roles without updating this policy. + +```json +{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam:::role/-*" + }] +} +``` + + + Choose a naming convention for your target roles (e.g., `pam-*`, `infisical-*`, or `privileged-*`). This allows you to add new accounts without modifying the PAM Role's permissions. + + +### 1.2 Create the PAM Role with Trust Policy + +Create an IAM role (e.g., `InfisicalPAMRole`) with: +- The permissions policy from step 1.1 attached +- The following trust policy: + +```json +{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam:::root" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "" + } + } + }] +} +``` + + + **Security Best Practice**: Always use the External ID condition. This prevents confused deputy attacks where another Infisical customer could potentially trick Infisical into assuming your role. + + +**Infisical AWS Account IDs:** +| Region | Account ID | +|--------|------------| +| US | `381492033652` | +| EU | `345594589636` | + + + **For Dedicated Instances**: Your AWS account ID differs from the ones listed above. Please contact Infisical support to obtain your dedicated AWS account ID. + + +### 1.3 Create the Resource in Infisical + +1. Navigate to your PAM project and go to the **Resources** tab +2. Click **Add Resource** and select **AWS IAM** +3. Enter a name for the resource (e.g., `production-aws`) +4. Enter the **PAM Role ARN** - the ARN of the role you created in step 1.2 + +![Create AWS IAM Resource](/images/pam/resources/aws-iam/create-resource.png) + +Clicking **Create Resource** will validate that Infisical can assume the PAM Role. If the connection fails, verify: +- The trust policy has the correct Infisical AWS account ID +- The External ID matches your project ID +- The role ARN is correct + +## Step 2: Create PAM Accounts + +A PAM Account represents a specific Target Role that users can request access to. You can create multiple accounts per resource, each pointing to a different target role with different permission levels. + +### 2.1 Create the Target Role Trust Policy + +Each target role needs a trust policy that allows your PAM Role to assume it: + +```json +{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam:::role/InfisicalPAMRole" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "" + } + } + }] +} +``` + + + **Tip**: If your target role name matches the wildcard pattern in your PAM Role's permissions policy (e.g., `pam-readonly`), you don't need to update the PAM Role's permissions. Just ensure the target role's trust policy is correct. + + +### 2.2 Create the Account in Infisical + +1. Navigate to the **Accounts** tab in your PAM project +2. Click **Add Account** and select the AWS IAM resource you created +3. Fill in the account details: + + + A friendly name for this account (e.g., `readonly`, `admin`, `developer`) + + + + Optional description of what this account is used for + + + + The ARN of the IAM role users will assume (e.g., `arn:aws:iam::123456789012:role/pam-readonly`) + + + + Session duration using human-readable format (e.g., `15m`, `30m`, `1h`). Minimum 15 minutes, maximum 1 hour. + + + Due to AWS role chaining limitations, the maximum session duration is **1 hour**, regardless of the target role's configured maximum session duration. + + + +![Create AWS IAM Account](/images/pam/resources/aws-iam/create-account.png) + +## Step 3: Access the AWS Console + +Once your resource and accounts are configured, users can request access through Infisical: + +1. Navigate to the **Accounts** tab +2. Find the AWS Console account you want to access +3. Click the **Access** button + +Infisical will: +1. Assume the PAM Role using your project's External ID +2. Assume the Target Role using role chaining +3. Generate a federated sign-in URL +4. Open the AWS Console in a new browser tab + +The user will be signed into the AWS Console with the permissions of the Target Role. + +## Session Behavior + +### Session Duration + +The session duration is set when creating the account and applies to all access requests. You can specify the duration using human-readable formats like `15m`, `30m`, or `1h`. Due to AWS role chaining limitations: + +- **Minimum**: 15 minutes (`15m`) +- **Maximum**: 1 hour (`1h`) + +### Session Tracking + +Infisical tracks: +- When the session was created +- Who accessed which role +- When the session expires + + + **Important**: AWS Console sessions cannot be terminated early. Once a federated URL is generated, the session remains valid until the configured duration expires. However, you can [revoke active sessions](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_revoke-sessions.html) by modifying the role's trust policy. + + +### CloudTrail Integration + +All actions performed in the AWS Console are logged in [AWS CloudTrail](https://console.aws.amazon.com/cloudtrail). The session is identified by the `RoleSessionName`, which includes the user's email address for attribution: + +``` +arn:aws:sts::123456789012:assumed-role/pam-readonly/user@example.com +``` + +This allows you to correlate Infisical PAM sessions with CloudTrail logs for complete audit visibility. + +## Example: Multi-Environment Setup + +A typical setup might include: + +**Resource**: `aws-production` (PAM Role in production account) +- **Account**: `readonly` → `arn:aws:iam::111111111111:role/pam-readonly` +- **Account**: `developer` → `arn:aws:iam::111111111111:role/pam-developer` +- **Account**: `admin` → `arn:aws:iam::111111111111:role/pam-admin` + +**Resource**: `aws-staging` (PAM Role in staging account) +- **Account**: `full-access` → `arn:aws:iam::222222222222:role/pam-full-access` + +Users can then be granted access to specific accounts based on their role and the approval workflows configured in Infisical. \ No newline at end of file diff --git a/docs/images/pam/resources/aws-iam/create-account.png b/docs/images/pam/resources/aws-iam/create-account.png new file mode 100644 index 000000000..4d2203667 Binary files /dev/null and b/docs/images/pam/resources/aws-iam/create-account.png differ diff --git a/docs/images/pam/resources/aws-iam/create-resource.png b/docs/images/pam/resources/aws-iam/create-resource.png new file mode 100644 index 000000000..766b86ffc Binary files /dev/null and b/docs/images/pam/resources/aws-iam/create-resource.png differ diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/AwsIamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/AwsIamAccountForm.tsx index 677fb9e90..90e6cfa2e 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/AwsIamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/AwsIamAccountForm.tsx @@ -2,8 +2,10 @@ 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 ms from "ms"; import { z } from "zod"; +import { TtlFormLabel } from "@app/components/features"; import { Accordion, AccordionContent, @@ -25,6 +27,9 @@ import { import { GenericAccountFields, genericAccountFieldsSchema } from "./GenericAccountFields"; +const AWS_STS_MIN_SESSION_DURATION = 900; // 15 minutes +const AWS_STS_MAX_SESSION_DURATION_ROLE_CHAINING = 3600; // 1 hour + type Props = { account?: TAwsIamAccount; resourceId?: string; @@ -42,12 +47,29 @@ const AwsIamCredentialsSchema = z.object({ .refine((val) => arnRoleRegex.test(val), { 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) - defaultSessionDuration: z.coerce - .number() - .min(900, "Minimum session duration is 900 seconds (15 minutes)") - .max(3600, "Maximum session duration is 3600 seconds (1 hour)") - .default(3600) + defaultSessionDuration: z.string().superRefine((val, ctx) => { + const valMs = ms(val); + if (typeof valMs !== "number" || valMs <= 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Invalid duration format. Use formats like 15m, 30m, 1h" + }); + return; + } + const valSeconds = valMs / 1000; + if (valSeconds < AWS_STS_MIN_SESSION_DURATION) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Minimum session duration is 15 minutes (15m)" + }); + } + if (valSeconds > AWS_STS_MAX_SESSION_DURATION_ROLE_CHAINING) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Maximum session duration is 1 hour (1h) due to AWS role chaining" + }); + } + }) }); const formSchema = genericAccountFieldsSchema.extend({ @@ -87,16 +109,36 @@ export const AwsIamAccountForm = ({ account, resourceId, resourceType, onSubmit }] }`; + // Convert seconds to human-readable format for existing accounts + const getDefaultSessionDuration = () => { + if (account?.credentials?.defaultSessionDuration) { + const seconds = account.credentials.defaultSessionDuration; + if (seconds >= 3600 && seconds % 3600 === 0) { + return `${seconds / 3600}h`; + } + return `${seconds / 60}m`; + } + return "1h"; + }; + const form = useForm({ resolver: zodResolver(formSchema), - defaultValues: account ?? { - name: "", - description: "", - credentials: { - targetRoleArn: "", - defaultSessionDuration: 3600 - } - } + defaultValues: account + ? { + ...account, + credentials: { + ...account.credentials, + defaultSessionDuration: getDefaultSessionDuration() + } + } + : { + name: "", + description: "", + credentials: { + targetRoleArn: "", + defaultSessionDuration: "1h" + } + } }); const { @@ -105,9 +147,22 @@ export const AwsIamAccountForm = ({ account, resourceId, resourceType, onSubmit formState: { isSubmitting, isDirty } } = form; + const handleFormSubmit = async (formData: FormData) => { + const durationMs = ms(formData.credentials.defaultSessionDuration); + const durationSeconds = Math.floor(durationMs / 1000); + + await onSubmit({ + ...formData, + credentials: { + ...formData.credentials, + defaultSessionDuration: durationSeconds + } + } as any); + }; + return ( -
+
@@ -139,12 +194,12 @@ export const AwsIamAccountForm = ({ account, resourceId, resourceType, onSubmit render={({ field, fieldState: { error } }) => ( } > - + )} />