Merge pull request #5001 from Infisical/chore/aws-iam-pam-docs
improvement(pam): add AWS IAM resource documentation and form enhancements
@@ -795,6 +795,12 @@
|
|||||||
"documentation/platform/pam/product-reference/session-recording",
|
"documentation/platform/pam/product-reference/session-recording",
|
||||||
"documentation/platform/pam/product-reference/credential-rotation"
|
"documentation/platform/pam/product-reference/credential-rotation"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Resources",
|
||||||
|
"pages": [
|
||||||
|
"documentation/platform/pam/resources/aws-iam"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,258 @@
|
|||||||
|
---
|
||||||
|
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 Resource Role as Resource Role<br/>(Your AWS Account)
|
||||||
|
participant Target Role as Target Role<br/>(Your AWS Account)
|
||||||
|
participant Console as AWS Console
|
||||||
|
|
||||||
|
User->>Infisical: Request AWS Console access
|
||||||
|
Infisical->>Resource Role: AssumeRole (with ExternalId)
|
||||||
|
Resource 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. **Resource 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 Resource Role assumes this role on behalf of the user.
|
||||||
|
|
||||||
|
3. **Role Chaining**: Infisical uses AWS role chaining - it first assumes the Resource 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).
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
**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.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
### 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/[email protected]
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows you to correlate Infisical PAM sessions with CloudTrail logs for complete audit visibility.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before configuring AWS Console access in Infisical PAM, you need to set up two IAM roles in your AWS account:
|
||||||
|
|
||||||
|
1. **Resource Role** - Trusted by Infisical, can assume target roles
|
||||||
|
2. **Target Role(s)** - The actual roles users will use in the console
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
**No Gateway Required**: Unlike database or SSH resources, AWS Console access does not require an Infisical Gateway. Infisical communicates directly with AWS APIs.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
## Create the PAM Resource
|
||||||
|
|
||||||
|
The PAM Resource represents the connection between Infisical and your AWS account. It contains the Resource Role that Infisical will assume.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Create the Resource Role Permissions Policy">
|
||||||
|
First, create an IAM policy that allows the Resource Role to assume your target roles. For simplicity, you can use a wildcard to allow assuming any role in your account:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "sts:AssumeRole",
|
||||||
|
"Resource": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/*"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Note>
|
||||||
|
**For more granular control**: If you want to restrict which roles the Resource Role can assume, replace the wildcard (`/*`) with a more specific pattern. For example:
|
||||||
|
- `arn:aws:iam::<YOUR_ACCOUNT_ID>:role/pam-*` to only allow roles with the `pam-` prefix
|
||||||
|
- `arn:aws:iam::<YOUR_ACCOUNT_ID>:role/infisical-*` to only allow roles with the `infisical-` prefix
|
||||||
|
|
||||||
|
This allows you to limit the blast radius of the Resource Role's permissions.
|
||||||
|
</Note>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Create the Resource Role with Trust Policy">
|
||||||
|
Create an IAM role (e.g., `InfisicalResourceRole`) with:
|
||||||
|
- The permissions policy from the previous step attached
|
||||||
|
- The following trust policy:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": "arn:aws:iam::<INFISICAL_AWS_ACCOUNT_ID>:root"
|
||||||
|
},
|
||||||
|
"Action": "sts:AssumeRole",
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"sts:ExternalId": "<YOUR_INFISICAL_PROJECT_ID>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
**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.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
**Infisical AWS Account IDs:**
|
||||||
|
| Region | Account ID |
|
||||||
|
|--------|------------|
|
||||||
|
| US | `381492033652` |
|
||||||
|
| EU | `345594589636` |
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
**For Dedicated Instances**: Your AWS account ID differs from the ones listed above. Please contact Infisical support to obtain your dedicated AWS account ID.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
**For Self-Hosted Instances**: Use the AWS account ID where your Infisical instance is deployed. This is the account that hosts your Infisical infrastructure and will be assuming the Resource Role.
|
||||||
|
</Note>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="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 **Resource Role ARN** - the ARN of the role you created in the previous step
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Clicking **Create Resource** will validate that Infisical can assume the Resource 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>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Create the Target Role Trust Policy">
|
||||||
|
Each target role needs a trust policy that allows your Resource Role to assume it:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/InfisicalResourceRole"
|
||||||
|
},
|
||||||
|
"Action": "sts:AssumeRole",
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"sts:ExternalId": "<YOUR_INFISICAL_PROJECT_ID>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<ParamField path="Name" type="string" required>
|
||||||
|
A friendly name for this account (e.g., `readonly`, `admin`, `developer`)
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Description" type="string">
|
||||||
|
Optional description of what this account is used for
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Target Role ARN" type="string" required>
|
||||||
|
The ARN of the IAM role users will assume (e.g., `arn:aws:iam::123456789012:role/pam-readonly`)
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Default Session Duration" type="string" required>
|
||||||
|
Session duration using human-readable format (e.g., `15m`, `30m`, `1h`). Minimum 15 minutes, maximum 1 hour.
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Due to AWS role chaining limitations, the maximum session duration is **1 hour**, regardless of the target role's configured maximum session duration.
|
||||||
|
</Warning>
|
||||||
|
</ParamField>
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Access the AWS Console
|
||||||
|
|
||||||
|
Once your resource and accounts are configured, users can request access through Infisical:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to Accounts">
|
||||||
|
Go to the **Accounts** tab in your PAM project.
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Find the Account">
|
||||||
|
Find the AWS Console account you want to access.
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Request Access">
|
||||||
|
Click the **Access** button.
|
||||||
|
|
||||||
|
Infisical will:
|
||||||
|
1. Assume the Resource 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.
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 213 KiB |
@@ -2,8 +2,10 @@ import { Controller, FormProvider, useForm } from "react-hook-form";
|
|||||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TtlFormLabel } from "@app/components/features";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
@@ -25,11 +27,23 @@ import {
|
|||||||
|
|
||||||
import { GenericAccountFields, genericAccountFieldsSchema } from "./GenericAccountFields";
|
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 SubmitData = {
|
||||||
|
name: string;
|
||||||
|
description?: string | null;
|
||||||
|
credentials: {
|
||||||
|
targetRoleArn: string;
|
||||||
|
defaultSessionDuration: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
account?: TAwsIamAccount;
|
account?: TAwsIamAccount;
|
||||||
resourceId?: string;
|
resourceId?: string;
|
||||||
resourceType?: PamResourceType;
|
resourceType?: PamResourceType;
|
||||||
onSubmit: (formData: FormData) => Promise<void>;
|
onSubmit: (formData: SubmitData) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const arnRoleRegex = /^arn:aws:iam::\d{12}:role\/[\w+=,.@/-]+$/;
|
const arnRoleRegex = /^arn:aws:iam::\d{12}:role\/[\w+=,.@/-]+$/;
|
||||||
@@ -42,12 +56,29 @@ const AwsIamCredentialsSchema = z.object({
|
|||||||
.refine((val) => arnRoleRegex.test(val), {
|
.refine((val) => arnRoleRegex.test(val), {
|
||||||
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)
|
defaultSessionDuration: z.string().superRefine((val, ctx) => {
|
||||||
defaultSessionDuration: z.coerce
|
const valMs = ms(val);
|
||||||
.number()
|
if (typeof valMs !== "number" || valMs <= 0) {
|
||||||
.min(900, "Minimum session duration is 900 seconds (15 minutes)")
|
ctx.addIssue({
|
||||||
.max(3600, "Maximum session duration is 3600 seconds (1 hour)")
|
code: z.ZodIssueCode.custom,
|
||||||
.default(3600)
|
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({
|
const formSchema = genericAccountFieldsSchema.extend({
|
||||||
@@ -66,17 +97,17 @@ export const AwsIamAccountForm = ({ account, resourceId, resourceType, onSubmit
|
|||||||
enabled: !!resourceIdToFetch && !!resourceTypeToFetch
|
enabled: !!resourceIdToFetch && !!resourceTypeToFetch
|
||||||
});
|
});
|
||||||
|
|
||||||
const pamRoleArn =
|
const resourceRoleArn =
|
||||||
(resource?.resourceType === PamResourceType.AwsIam &&
|
(resource?.resourceType === PamResourceType.AwsIam &&
|
||||||
(resource as TAwsIamResource).connectionDetails?.roleArn) ||
|
(resource as TAwsIamResource).connectionDetails?.roleArn) ||
|
||||||
"arn:aws:iam::<YOUR_ACCOUNT_ID>:role/<YOUR_PAM_ROLE_NAME>";
|
"arn:aws:iam::<YOUR_ACCOUNT_ID>:role/<YOUR_RESOURCE_ROLE_NAME>";
|
||||||
|
|
||||||
const targetRoleTrustPolicy = `{
|
const targetRoleTrustPolicy = `{
|
||||||
"Version": "2012-10-17",
|
"Version": "2012-10-17",
|
||||||
"Statement": [{
|
"Statement": [{
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Principal": {
|
"Principal": {
|
||||||
"AWS": "${pamRoleArn}"
|
"AWS": "${resourceRoleArn}"
|
||||||
},
|
},
|
||||||
"Action": "sts:AssumeRole",
|
"Action": "sts:AssumeRole",
|
||||||
"Condition": {
|
"Condition": {
|
||||||
@@ -87,16 +118,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<FormData>({
|
const form = useForm<FormData>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: account ?? {
|
defaultValues: account
|
||||||
name: "",
|
? {
|
||||||
description: "",
|
...account,
|
||||||
credentials: {
|
credentials: {
|
||||||
targetRoleArn: "",
|
...account.credentials,
|
||||||
defaultSessionDuration: 3600
|
defaultSessionDuration: getDefaultSessionDuration()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
credentials: {
|
||||||
|
targetRoleArn: "",
|
||||||
|
defaultSessionDuration: "1h"
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -105,9 +156,23 @@ export const AwsIamAccountForm = ({ account, resourceId, resourceType, onSubmit
|
|||||||
formState: { isSubmitting, isDirty }
|
formState: { isSubmitting, isDirty }
|
||||||
} = form;
|
} = form;
|
||||||
|
|
||||||
|
const handleFormSubmit = async (formData: FormData) => {
|
||||||
|
const durationMs = ms(formData.credentials.defaultSessionDuration);
|
||||||
|
const durationSeconds = Math.floor(durationMs / 1000);
|
||||||
|
|
||||||
|
await onSubmit({
|
||||||
|
name: formData.name,
|
||||||
|
description: formData.description,
|
||||||
|
credentials: {
|
||||||
|
targetRoleArn: formData.credentials.targetRoleArn,
|
||||||
|
defaultSessionDuration: durationSeconds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||||
<GenericAccountFields />
|
<GenericAccountFields />
|
||||||
|
|
||||||
<div className="mb-4 rounded-sm border border-mineshaft-600 bg-mineshaft-700/70 p-3">
|
<div className="mb-4 rounded-sm border border-mineshaft-600 bg-mineshaft-700/70 p-3">
|
||||||
@@ -139,12 +204,12 @@ export const AwsIamAccountForm = ({ account, resourceId, resourceType, onSubmit
|
|||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
className="mb-0"
|
className="mb-0"
|
||||||
helperText="In seconds. Min 900 (15m), max 3600 (1h) due to AWS role chaining limit."
|
helperText="Min 15m, max 1h due to AWS role chaining limit."
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
label="Default Session Duration (seconds)"
|
label={<TtlFormLabel label="Default Session Duration" />}
|
||||||
>
|
>
|
||||||
<Input {...field} type="number" placeholder="3600" />
|
<Input {...field} placeholder="1h" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -164,10 +229,9 @@ export const AwsIamAccountForm = ({ account, resourceId, resourceType, onSubmit
|
|||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="px-4 pb-2.5">
|
<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 PAM role (created in the
|
The target role must have a trust policy that allows the Resource Role (created in
|
||||||
"Resources" tab) to assume it. If your target role name follows the
|
the "Resources" tab) to assume it. Ensure the target role's trust
|
||||||
wildcard pattern you defined in the PAM role's permissions policy, no
|
policy includes the Resource Role as a trusted principal.
|
||||||
additional changes are needed.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="mb-2 text-sm font-medium text-mineshaft-200">
|
<p className="mb-2 text-sm font-medium text-mineshaft-200">
|
||||||
@@ -182,12 +246,11 @@ export const AwsIamAccountForm = ({ account, resourceId, resourceType, onSubmit
|
|||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-mineshaft-400">
|
<p className="text-xs text-mineshaft-400">
|
||||||
<strong>Note:</strong> The Principal role ARN shown above is from the PAM Resource
|
<strong>Note:</strong> The Principal role ARN shown above is from the Resource
|
||||||
selected for this account. The External ID{" "}
|
selected for this account. The External ID{" "}
|
||||||
<code className="rounded bg-mineshaft-700 px-1 font-bold">{projectId}</code> is your
|
<code className="rounded bg-mineshaft-700 px-1 font-bold">{projectId}</code> is your
|
||||||
current project ID. If your target role name doesn't match the wildcard pattern
|
current project ID. If you configured granular permissions in your Resource
|
||||||
in your PAM Resource's role's permissions policy, you'll need to
|
Role's policy, ensure this target role's ARN is included.
|
||||||
update that policy to include this role's ARN.
|
|
||||||
</p>
|
</p>
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
import { DocumentationLinkBadge } from "@app/components/v3";
|
||||||
import { PAM_RESOURCE_TYPE_MAP, PamResourceType } from "@app/hooks/api/pam";
|
import { PAM_RESOURCE_TYPE_MAP, PamResourceType } from "@app/hooks/api/pam";
|
||||||
|
|
||||||
|
const PAM_ACCOUNT_DOCS_MAP: Partial<Record<PamResourceType, string>> = {
|
||||||
|
[PamResourceType.AwsIam]: "aws-iam#create-pam-accounts"
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
resourceName: string;
|
resourceName: string;
|
||||||
resourceType: PamResourceType;
|
resourceType: PamResourceType;
|
||||||
@@ -8,6 +13,7 @@ type Props = {
|
|||||||
|
|
||||||
export const PamAccountHeader = ({ resourceName, resourceType, onBack }: Props) => {
|
export const PamAccountHeader = ({ resourceName, resourceType, onBack }: Props) => {
|
||||||
const details = PAM_RESOURCE_TYPE_MAP[resourceType];
|
const details = PAM_RESOURCE_TYPE_MAP[resourceType];
|
||||||
|
const docsPath = PAM_ACCOUNT_DOCS_MAP[resourceType];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 flex w-full items-start gap-2 border-b border-mineshaft-500 pb-4">
|
<div className="mb-4 flex w-full items-start gap-2 border-b border-mineshaft-500 pb-4">
|
||||||
@@ -17,7 +23,14 @@ export const PamAccountHeader = ({ resourceName, resourceType, onBack }: Props)
|
|||||||
className="h-12 w-12 rounded-md bg-bunker-500 p-2"
|
className="h-12 w-12 rounded-md bg-bunker-500 p-2"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center text-mineshaft-300">{resourceName}</div>
|
<div className="flex items-center gap-x-2 text-mineshaft-300">
|
||||||
|
{resourceName}
|
||||||
|
{docsPath && (
|
||||||
|
<DocumentationLinkBadge
|
||||||
|
href={`https://infisical.com/docs/documentation/platform/pam/resources/${docsPath}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<p className="text-sm leading-4 text-mineshaft-400">{details.name} resource</p>
|
<p className="text-sm leading-4 text-mineshaft-400">{details.name} resource</p>
|
||||||
</div>
|
</div>
|
||||||
{onBack && (
|
{onBack && (
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const AwsIamConnectionDetailsSchema = z.object({
|
|||||||
roleArn: z
|
roleArn: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1, "PAM Role ARN is required")
|
.min(1, "Resource Role ARN is required")
|
||||||
.refine((val) => arnRoleRegex.test(val), {
|
.refine((val) => arnRoleRegex.test(val), {
|
||||||
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'"
|
||||||
})
|
})
|
||||||
@@ -57,7 +57,7 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
"Statement": [{
|
"Statement": [{
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": "sts:AssumeRole",
|
"Action": "sts:AssumeRole",
|
||||||
"Resource": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/<YOUR_PREFIX>-*"
|
"Resource": "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/*"
|
||||||
}]
|
}]
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
@@ -116,12 +116,15 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
helperText="The ARN of the Infisical PAM role that can assume target roles"
|
helperText="The ARN of the Infisical Resource Role that can assume target roles"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
label="PAM Role ARN"
|
label="Resource Role ARN"
|
||||||
>
|
>
|
||||||
<Input placeholder="arn:aws:iam::123456789012:role/InfisicalPAMRole" {...field} />
|
<Input
|
||||||
|
placeholder="arn:aws:iam::123456789012:role/InfisicalResourceRole"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -148,12 +151,12 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
Step 1: Create a permissions policy for assuming target roles
|
Step 1: Create a permissions policy for assuming target roles
|
||||||
</p>
|
</p>
|
||||||
<p className="mb-3 text-sm text-mineshaft-300">
|
<p className="mb-3 text-sm text-mineshaft-300">
|
||||||
This policy allows the PAM role to assume target roles. We recommend using a
|
This policy allows the Resource Role to assume target roles. For simplicity, use a
|
||||||
wildcard pattern (e.g.,{" "}
|
wildcard to allow assuming any role in your account. For more granular control,
|
||||||
<code className="rounded bg-mineshaft-700 px-1 text-xs">pam-*</code> or{" "}
|
replace <code className="rounded bg-mineshaft-700 px-1 text-xs">*</code> with a
|
||||||
<code className="rounded bg-mineshaft-700 px-1 text-xs">privileged-*</code>) so you
|
specific pattern like{" "}
|
||||||
can add new accounts without updating this policy. Choose a prefix that fits your
|
<code className="rounded bg-mineshaft-700 px-1 text-xs">/pam-*</code> or{" "}
|
||||||
naming conventions.
|
<code className="rounded bg-mineshaft-700 px-1 text-xs">/infisical-*</code>.
|
||||||
</p>
|
</p>
|
||||||
<div className="relative mb-4">
|
<div className="relative mb-4">
|
||||||
<div className="absolute top-1 right-1">
|
<div className="absolute top-1 right-1">
|
||||||
@@ -165,12 +168,12 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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 Resource Role with a trust policy
|
||||||
</p>
|
</p>
|
||||||
<p className="mb-3 text-sm text-mineshaft-300">
|
<p className="mb-3 text-sm text-mineshaft-300">
|
||||||
Create an IAM role (e.g.,{" "}
|
Create an IAM role (e.g.,{" "}
|
||||||
<code className="rounded bg-mineshaft-700 px-1 text-xs">InfisicalPAMRole</code>)
|
<code className="rounded bg-mineshaft-700 px-1 text-xs">InfisicalResourceRole</code>
|
||||||
with the permissions policy above and the following trust policy:
|
) with the permissions policy above and the following trust policy:
|
||||||
</p>
|
</p>
|
||||||
<div className="relative mb-4">
|
<div className="relative mb-4">
|
||||||
<div className="absolute top-1 right-3">
|
<div className="absolute top-1 right-3">
|
||||||
@@ -189,11 +192,8 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
|
|||||||
<code className="rounded bg-mineshaft-700 px-1 font-bold">
|
<code className="rounded bg-mineshaft-700 px-1 font-bold">
|
||||||
{INFISICAL_AWS_ACCOUNT_EU}
|
{INFISICAL_AWS_ACCOUNT_EU}
|
||||||
</code>{" "}
|
</code>{" "}
|
||||||
for EU region. Replace{" "}
|
for EU region. For dedicated instances, contact Infisical support. For self-hosted
|
||||||
<code className="rounded bg-mineshaft-700 px-1 font-bold">
|
instances, use your Infisical deployment's AWS account ID. The External ID{" "}
|
||||||
<INFISICAL_AWS_ACCOUNT_ID>
|
|
||||||
</code>{" "}
|
|
||||||
with the appropriate Infisical AWS account ID for your region. The External ID{" "}
|
|
||||||
<code className="rounded bg-mineshaft-700 px-1 font-bold">{projectId}</code> is your
|
<code className="rounded bg-mineshaft-700 px-1 font-bold">{projectId}</code> is your
|
||||||
current project ID.
|
current project ID.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
import { DocumentationLinkBadge } from "@app/components/v3";
|
||||||
import { PAM_RESOURCE_TYPE_MAP, PamResourceType } from "@app/hooks/api/pam";
|
import { PAM_RESOURCE_TYPE_MAP, PamResourceType } from "@app/hooks/api/pam";
|
||||||
|
|
||||||
|
const PAM_RESOURCE_DOCS_MAP: Partial<Record<PamResourceType, string>> = {
|
||||||
|
[PamResourceType.AwsIam]: "aws-iam#create-the-pam-resource"
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
resourceType: PamResourceType;
|
resourceType: PamResourceType;
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
@@ -7,6 +12,7 @@ type Props = {
|
|||||||
|
|
||||||
export const PamResourceHeader = ({ resourceType, onBack }: Props) => {
|
export const PamResourceHeader = ({ resourceType, onBack }: Props) => {
|
||||||
const details = PAM_RESOURCE_TYPE_MAP[resourceType];
|
const details = PAM_RESOURCE_TYPE_MAP[resourceType];
|
||||||
|
const docsPath = PAM_RESOURCE_DOCS_MAP[resourceType];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 flex w-full items-start gap-2 border-b border-mineshaft-500 pb-4">
|
<div className="mb-4 flex w-full items-start gap-2 border-b border-mineshaft-500 pb-4">
|
||||||
@@ -16,7 +22,14 @@ export const PamResourceHeader = ({ resourceType, onBack }: Props) => {
|
|||||||
className="h-12 w-12 rounded-md bg-bunker-500 p-2"
|
className="h-12 w-12 rounded-md bg-bunker-500 p-2"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center text-mineshaft-300">{details.name}</div>
|
<div className="flex items-center gap-x-2 text-mineshaft-300">
|
||||||
|
{details.name}
|
||||||
|
{docsPath && (
|
||||||
|
<DocumentationLinkBadge
|
||||||
|
href={`https://infisical.com/docs/documentation/platform/pam/resources/${docsPath}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<p className="text-sm leading-4 text-mineshaft-400">Resource</p>
|
<p className="text-sm leading-4 text-mineshaft-400">Resource</p>
|
||||||
</div>
|
</div>
|
||||||
{onBack && (
|
{onBack && (
|
||||||
|
|||||||