Merge pull request #4471 from Infisical/ENG-3630

Dynamic Secrets: add Temporary Credentials for AWS IAM Roles
This commit is contained in:
carlosmonastyrski
2025-09-16 12:55:37 -03:00
committed by GitHub
7 changed files with 936 additions and 405 deletions

View File

@@ -16,7 +16,7 @@ import {
PutUserPolicyCommand,
RemoveUserFromGroupCommand
} from "@aws-sdk/client-iam";
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
import { AssumeRoleCommand, GetSessionTokenCommand, STSClient } from "@aws-sdk/client-sts";
import { z } from "zod";
import { CustomAWSHasher } from "@app/lib/aws/hashing";
@@ -26,9 +26,12 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { sanitizeString } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { AwsIamAuthType, DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
import { AwsIamAuthType, AwsIamCredentialType, DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
import { compileUsernameTemplate } from "./templateUtils";
// AWS STS duration constants (in seconds)
const AWS_STS_MIN_DURATION = 900;
const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => {
const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
@@ -120,6 +123,58 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
const validateConnection = async (inputs: unknown, { projectId }: { projectId: string }) => {
const providerInputs = await validateProviderInputs(inputs);
try {
if (providerInputs.credentialType === AwsIamCredentialType.TemporaryCredentials) {
if (providerInputs.method === AwsIamAuthType.AccessKey) {
const stsClient = new STSClient({
region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher,
credentials: {
accessKeyId: providerInputs.accessKey,
secretAccessKey: providerInputs.secretAccessKey
}
});
await stsClient.send(new GetSessionTokenCommand({ DurationSeconds: AWS_STS_MIN_DURATION }));
return true;
}
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
const appCfg = getConfig();
const stsClient = new STSClient({
region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher,
credentials:
appCfg.DYNAMIC_SECRET_AWS_ACCESS_KEY_ID && appCfg.DYNAMIC_SECRET_AWS_SECRET_ACCESS_KEY
? {
accessKeyId: appCfg.DYNAMIC_SECRET_AWS_ACCESS_KEY_ID,
secretAccessKey: appCfg.DYNAMIC_SECRET_AWS_SECRET_ACCESS_KEY
}
: undefined
});
await stsClient.send(
new AssumeRoleCommand({
RoleArn: providerInputs.roleArn,
RoleSessionName: `infisical-validation-${crypto.nativeCrypto.randomUUID()}`,
DurationSeconds: AWS_STS_MIN_DURATION,
ExternalId: projectId
})
);
return true;
}
if (providerInputs.method === AwsIamAuthType.IRSA) {
const stsClient = new STSClient({
region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher
});
await stsClient.send(new GetSessionTokenCommand({ DurationSeconds: AWS_STS_MIN_DURATION }));
return true;
}
}
const client = await $getClient(providerInputs, projectId);
const isConnected = await client
.send(new GetUserCommand({}))
@@ -137,7 +192,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
});
return isConnected;
} catch (err) {
const sensitiveTokens = [];
const sensitiveTokens: string[] = [];
if (providerInputs.method === AwsIamAuthType.AccessKey) {
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
}
@@ -163,102 +218,269 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
};
metadata: { projectId: string };
}) => {
const { inputs, usernameTemplate, metadata, identity } = data;
const { inputs, usernameTemplate, metadata, identity, expireAt } = data;
const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs, metadata.projectId);
const username = generateUsername(usernameTemplate, identity);
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
const awsTags = [{ Key: "createdBy", Value: "infisical-dynamic-secret" }];
if (providerInputs.credentialType === AwsIamCredentialType.TemporaryCredentials) {
try {
let stsClient: STSClient;
let entityId: string;
if (providerInputs.tags && Array.isArray(providerInputs.tags)) {
const additionalTags = providerInputs.tags.map((tag) => ({
Key: tag.key,
Value: tag.value
}));
awsTags.push(...additionalTags);
const currentTime = Date.now();
const requestedDuration = Math.floor((expireAt - currentTime) / 1000);
if (requestedDuration <= 0) {
throw new BadRequestError({ message: "Expiration time must be in the future" });
}
let durationSeconds: number;
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
durationSeconds = requestedDuration;
const appCfg = getConfig();
stsClient = new STSClient({
region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher,
credentials:
appCfg.DYNAMIC_SECRET_AWS_ACCESS_KEY_ID && appCfg.DYNAMIC_SECRET_AWS_SECRET_ACCESS_KEY
? {
accessKeyId: appCfg.DYNAMIC_SECRET_AWS_ACCESS_KEY_ID,
secretAccessKey: appCfg.DYNAMIC_SECRET_AWS_SECRET_ACCESS_KEY
}
: undefined
});
const assumeRoleRes = await stsClient.send(
new AssumeRoleCommand({
RoleArn: providerInputs.roleArn,
RoleSessionName: `infisical-temp-cred-${crypto.nativeCrypto.randomUUID()}`,
DurationSeconds: durationSeconds,
ExternalId: metadata.projectId
})
);
if (
!assumeRoleRes.Credentials?.AccessKeyId ||
!assumeRoleRes.Credentials?.SecretAccessKey ||
!assumeRoleRes.Credentials?.SessionToken
) {
throw new BadRequestError({ message: "Failed to assume role - verify credentials and role configuration" });
}
entityId = `assume-role-${alphaNumericNanoId(8)}`;
return {
entityId,
data: {
ACCESS_KEY: assumeRoleRes.Credentials.AccessKeyId,
SECRET_ACCESS_KEY: assumeRoleRes.Credentials.SecretAccessKey,
SESSION_TOKEN: assumeRoleRes.Credentials.SessionToken
}
};
}
if (providerInputs.method === AwsIamAuthType.AccessKey) {
durationSeconds = requestedDuration;
stsClient = new STSClient({
region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher,
credentials: {
accessKeyId: providerInputs.accessKey,
secretAccessKey: providerInputs.secretAccessKey
}
});
const sessionTokenRes = await stsClient.send(
new GetSessionTokenCommand({
DurationSeconds: durationSeconds
})
);
if (
!sessionTokenRes.Credentials?.AccessKeyId ||
!sessionTokenRes.Credentials?.SecretAccessKey ||
!sessionTokenRes.Credentials?.SessionToken
) {
throw new BadRequestError({ message: "Failed to get session token - verify credentials and permissions" });
}
entityId = `session-token-${alphaNumericNanoId(8)}`;
return {
entityId,
data: {
ACCESS_KEY: sessionTokenRes.Credentials.AccessKeyId,
SECRET_ACCESS_KEY: sessionTokenRes.Credentials.SecretAccessKey,
SESSION_TOKEN: sessionTokenRes.Credentials.SessionToken
}
};
}
if (providerInputs.method === AwsIamAuthType.IRSA) {
durationSeconds = requestedDuration;
stsClient = new STSClient({
region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher
});
const sessionTokenRes = await stsClient.send(
new GetSessionTokenCommand({
DurationSeconds: durationSeconds
})
);
if (
!sessionTokenRes.Credentials?.AccessKeyId ||
!sessionTokenRes.Credentials?.SecretAccessKey ||
!sessionTokenRes.Credentials?.SessionToken
) {
throw new BadRequestError({
message: "Failed to get session token - verify IRSA credentials and permissions"
});
}
entityId = `irsa-session-${alphaNumericNanoId(8)}`;
return {
entityId,
data: {
ACCESS_KEY: sessionTokenRes.Credentials.AccessKeyId,
SECRET_ACCESS_KEY: sessionTokenRes.Credentials.SecretAccessKey,
SESSION_TOKEN: sessionTokenRes.Credentials.SessionToken
}
};
}
throw new BadRequestError({ message: "Unsupported authentication method for temporary credentials" });
} catch (err) {
const sensitiveTokens: string[] = [];
if (providerInputs.method === AwsIamAuthType.AccessKey) {
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
}
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
sensitiveTokens.push(providerInputs.roleArn);
}
let errorMessage = (err as Error)?.message || "Unknown error";
if (err && typeof err === "object" && "name" in err && "$metadata" in err) {
const awsError = err as { name?: string; message?: string; $metadata?: object };
if (awsError.name) {
errorMessage = `${awsError.name}: ${errorMessage}`;
}
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: errorMessage,
tokens: sensitiveTokens
});
throw new BadRequestError({
message: `Failed to create temporary credentials: ${sanitizedErrorMessage}`
});
}
}
try {
const createUserRes = await client.send(
new CreateUserCommand({
Path: awsPath,
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
Tags: awsTags,
UserName: username
})
);
if (providerInputs.credentialType === AwsIamCredentialType.IamUser) {
const client = await $getClient(providerInputs, metadata.projectId);
if (!createUserRes.User) throw new BadRequestError({ message: "Failed to create AWS IAM User" });
if (userGroups) {
await Promise.all(
userGroups
.split(",")
.filter(Boolean)
.map((group) =>
client.send(new AddUserToGroupCommand({ UserName: createUserRes?.User?.UserName, GroupName: group }))
)
);
const username = generateUsername(usernameTemplate, identity);
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
const awsTags = [{ Key: "createdBy", Value: "infisical-dynamic-secret" }];
if (providerInputs.tags && Array.isArray(providerInputs.tags)) {
const additionalTags = providerInputs.tags.map((tag) => ({
Key: tag.key,
Value: tag.value
}));
awsTags.push(...additionalTags);
}
if (policyArns) {
await Promise.all(
policyArns
.split(",")
.filter(Boolean)
.map((policyArn) =>
client.send(
new AttachUserPolicyCommand({ UserName: createUserRes?.User?.UserName, PolicyArn: policyArn })
)
)
);
}
if (policyDocument) {
await client.send(
new PutUserPolicyCommand({
UserName: createUserRes.User.UserName,
PolicyName: `infisical-dynamic-policy-${alphaNumericNanoId(4)}`,
PolicyDocument: policyDocument
try {
const createUserRes = await client.send(
new CreateUserCommand({
Path: awsPath,
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
Tags: awsTags,
UserName: username
})
);
}
const createAccessKeyRes = await client.send(
new CreateAccessKeyCommand({
UserName: createUserRes.User.UserName
})
);
if (!createAccessKeyRes.AccessKey)
throw new BadRequestError({ message: "Failed to create AWS IAM User access key" });
return {
entityId: username,
data: {
ACCESS_KEY: createAccessKeyRes.AccessKey.AccessKeyId,
SECRET_ACCESS_KEY: createAccessKeyRes.AccessKey.SecretAccessKey,
USERNAME: username
if (!createUserRes.User) throw new BadRequestError({ message: "Failed to create AWS IAM User" });
if (userGroups) {
await Promise.all(
userGroups
.split(",")
.filter(Boolean)
.map((group) =>
client.send(new AddUserToGroupCommand({ UserName: createUserRes?.User?.UserName, GroupName: group }))
)
);
}
};
} catch (err) {
const sensitiveTokens = [username];
if (providerInputs.method === AwsIamAuthType.AccessKey) {
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
if (policyArns) {
await Promise.all(
policyArns
.split(",")
.filter(Boolean)
.map((policyArn) =>
client.send(
new AttachUserPolicyCommand({ UserName: createUserRes?.User?.UserName, PolicyArn: policyArn })
)
)
);
}
if (policyDocument) {
await client.send(
new PutUserPolicyCommand({
UserName: createUserRes.User.UserName,
PolicyName: `infisical-dynamic-policy-${alphaNumericNanoId(4)}`,
PolicyDocument: policyDocument
})
);
}
const createAccessKeyRes = await client.send(
new CreateAccessKeyCommand({
UserName: createUserRes.User.UserName
})
);
if (!createAccessKeyRes.AccessKey)
throw new BadRequestError({ message: "Failed to create AWS IAM User access key" });
return {
entityId: username,
data: {
ACCESS_KEY: createAccessKeyRes.AccessKey.AccessKeyId,
SECRET_ACCESS_KEY: createAccessKeyRes.AccessKey.SecretAccessKey,
USERNAME: username
}
};
} catch (err) {
const sensitiveTokens = [username];
if (providerInputs.method === AwsIamAuthType.AccessKey) {
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
}
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
sensitiveTokens.push(providerInputs.roleArn);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: sensitiveTokens
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
sensitiveTokens.push(providerInputs.roleArn);
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: sensitiveTokens
});
throw new BadRequestError({
message: `Failed to create lease from provider: ${sanitizedErrorMessage}`
});
}
throw new BadRequestError({ message: "Invalid credential type specified" });
};
const revoke = async (inputs: unknown, entityId: string, metadata: { projectId: string }) => {
const providerInputs = await validateProviderInputs(inputs);
if (providerInputs.credentialType === AwsIamCredentialType.TemporaryCredentials) {
return { entityId };
}
const client = await $getClient(providerInputs, metadata.projectId);
const username = entityId;

View File

@@ -32,6 +32,11 @@ export enum AwsIamAuthType {
IRSA = "irsa"
}
export enum AwsIamCredentialType {
IamUser = "iam-user",
TemporaryCredentials = "temporary-credentials"
}
export enum ElasticSearchAuthTypes {
User = "user",
ApiKey = "api-key"
@@ -203,6 +208,7 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
z.discriminatedUnion("method", [
z.object({
method: z.literal(AwsIamAuthType.AccessKey),
credentialType: z.nativeEnum(AwsIamCredentialType).default(AwsIamCredentialType.IamUser),
accessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1),
region: z.string().trim().min(1),
@@ -215,6 +221,7 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
}),
z.object({
method: z.literal(AwsIamAuthType.AssumeRole),
credentialType: z.nativeEnum(AwsIamCredentialType).default(AwsIamCredentialType.IamUser),
roleArn: z.string().trim().min(1, "Role ARN required"),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
@@ -226,6 +233,7 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
}),
z.object({
method: z.literal(AwsIamAuthType.IRSA),
credentialType: z.nativeEnum(AwsIamCredentialType).default(AwsIamCredentialType.IamUser),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),

View File

@@ -3,49 +3,93 @@ title: "AWS IAM"
description: "Learn how to dynamically generate AWS IAM Users."
---
The Infisical AWS IAM dynamic secret allows you to generate AWS IAM Users on demand based on a configured AWS policy. Infisical supports several authentication methods to connect to your AWS account, including assuming an IAM Role, using IAM Roles for Service Accounts (IRSA) on EKS, or static Access Keys.
The Infisical AWS IAM dynamic secret allows you to generate AWS IAM Users and temporary credentials on demand based on a configured AWS policy. Infisical supports several authentication methods to connect to your AWS account, including assuming an IAM Role, using IAM Roles for Service Accounts (IRSA) on EKS, or static Access Keys.
## AWS STS Duration Limits
When using **Temporary Credentials**, AWS STS has specific maximum duration limits:
- **AssumeRole operations**: Maximum 1 hour (3600 seconds) when using temporary credentials
- **GetSessionToken operations** (Access Key & IRSA): Maximum 12 hours (43200 seconds)
<Info>
**Automatic Duration Adjustment**: If you specify a TTL that exceeds these AWS limits, Infisical will automatically use the maximum allowed duration instead of failing the operation. This ensures your dynamic secrets work reliably within AWS constraints.
</Info>
## Prerequisite
Infisical needs an AWS IAM principal (a user or a role) with the required permissions to create and manage other IAM users. This principal will be responsible for the lifecycle of the dynamically generated users.
Infisical needs an AWS IAM principal (a user or a role) with the required permissions to create and manage other IAM users and temporary credentials. This principal will be responsible for the lifecycle of the dynamically generated users and temporary credentials.
<Accordion title="Required IAM Permissions">
```json
{
"Version": "2012-10-17",
"Statement": [
<Tabs>
<Tab title="IAM User">
Required permissions for creating temporary IAM users:
```json
{
"Effect": "Allow",
"Action": [
"iam:AttachUserPolicy",
"iam:CreateAccessKey",
"iam:CreateUser",
"iam:DeleteAccessKey",
"iam:DeleteUser",
"iam:DeleteUserPolicy",
"iam:DetachUserPolicy",
"iam:GetUser",
"iam:ListAccessKeys",
"iam:ListAttachedUserPolicies",
"iam:ListGroupsForUser",
"iam:ListUserPolicies",
"iam:PutUserPolicy",
"iam:AddUserToGroup",
"iam:RemoveUserFromGroup",
"iam:TagUser"
],
"Resource": ["*"]
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:AttachUserPolicy",
"iam:CreateAccessKey",
"iam:CreateUser",
"iam:DeleteAccessKey",
"iam:DeleteUser",
"iam:DeleteUserPolicy",
"iam:DetachUserPolicy",
"iam:GetUser",
"iam:ListAccessKeys",
"iam:ListAttachedUserPolicies",
"iam:ListGroupsForUser",
"iam:ListUserPolicies",
"iam:PutUserPolicy",
"iam:AddUserToGroup",
"iam:RemoveUserFromGroup",
"iam:TagUser"
],
"Resource": ["*"]
}
]
}
]
}
```
```
To minimize managing user access you can attach a resource in format
To minimize managing user access you can attach a resource in format
> arn:aws:iam::\<account-id\>:user/\<aws-scope-path\>
> arn:aws:iam::\<account-id\>:user/\<aws-scope-path\>
Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** with a path to minimize managing user access.
Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** with a path to minimize managing user access.
</Tab>
<Tab title="Temporary Credentials">
Required permissions for Access Key and Assume Role methods:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:GetSessionToken",
"sts:AssumeRole"
],
"Resource": ["*"]
}
]
}
```
To minimize managing user access you can attach a resource in format
> arn:aws:iam::\<account-id\>:user/\<aws-scope-path\>
Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** with a path to minimize managing user access.
</Tab>
</Tabs>
</Accordion>
@@ -170,43 +214,76 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
Select *Assume Role* method.
</ParamField>
<ParamField path="Aws Role ARN" type="string" required>
The ARN of the AWS Role to assume.
<ParamField path="Credential Type" type="string" required>
Choose the credential generation approach:
- **IAM User (Default)**: Creates new temporary IAM users in your AWS account
- **Temporary Credentials**: Generates temporary credentials from your role connection
</ParamField>
<ParamField path="AWS IAM Path" type="string">
[IAM AWS Path](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) to scope created IAM User resource access.
<ParamField path="Aws Role ARN" type="string" required>
The ARN of the AWS Role to assume.
</ParamField>
<ParamField path="AWS Region" type="string" required>
The AWS data center region.
</ParamField>
<ParamField path="IAM User Permission Boundary" type="string" required>
The IAM Policy ARN of the [AWS Permissions Boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to attach to IAM users created in the role.
</ParamField>
<Tabs>
<Tab title="IAM User">
<ParamField path="AWS IAM Path" type="string">
[IAM AWS Path](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) to scope created IAM User resource access.
</ParamField>
<ParamField path="AWS IAM Groups" type="string">
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas
</ParamField>
<ParamField path="IAM User Permission Boundary" type="string">
The IAM Policy ARN of the [AWS Permissions Boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to attach to IAM users created in the role.
</ParamField>
<ParamField path="AWS Policy ARNs" type="string">
The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas
</ParamField>
<ParamField path="AWS IAM Groups" type="string">
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas.
</ParamField>
<ParamField path="AWS IAM Policy Document" type="string">
The AWS IAM inline policy that should be attached to the created users.
Multiple values can be provided by separating them with commas
</ParamField>
<ParamField path="AWS Policy ARNs" type="string">
The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas.
</ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
<ParamField path="AWS IAM Policy Document" type="string">
The AWS IAM inline policy that should be attached to the created users. Multiple values can be provided by separating them with commas.
</ParamField>
Allowed template variables are
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
Allowed template variables are:
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
- `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random N}}`: Random string of N characters
Allowed template functions are:
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
</ParamField>
<ParamField path="Tags" type="map<string, string>[]">
Tags to be added to the created IAM User resource.
</ParamField>
</Tab>
<Tab title="Temporary Credentials">
When **Credential Type** is set to **Temporary Credentials**:
<Info>
No additional configuration parameters are required. The generated credentials will:
- Inherit the permissions of the assumed role
- Include an AWS Session Token
- Be valid for the duration specified in Default TTL
</Info>
<Warning>
**Duration Limit**: AssumeRole temporary credentials are limited to 1 hour maximum by AWS. TTL values exceeding this limit will be automatically adjusted to 1 hour.
</Warning>
</Tab>
</Tabs>
</Step>
<Step title="Click 'Submit'">
@@ -232,6 +309,18 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you.
**Credentials format depends on your chosen credential type:**
**IAM User credential type:**
- AWS Username
- AWS Access Key ID
- AWS Secret Access Key
**Temporary Credentials credential type:**
- AWS Access Key ID
- AWS Secret Access Key
- AWS Session Token
![Provision Lease](/images/platform/dynamic-secrets/lease-values-aws-iam.png)
</Step>
</Steps>
@@ -342,36 +431,75 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
<ParamField path="Method" type="string" required>
Select *IRSA* method.
</ParamField>
<ParamField path="Credential Type" type="string" required>
Choose the credential generation approach:
- **IAM User**: Creates new temporary IAM users in your AWS account
- **Temporary Credentials**: Generates temporary credentials from your IRSA role connection
</ParamField>
<ParamField path="Aws Role ARN" type="string" required>
The ARN of the AWS IAM Role for the service account to assume.
</ParamField>
<ParamField path="AWS IAM Path" type="string">
[IAM AWS Path](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) to scope created IAM User resource access.
</ParamField>
<ParamField path="AWS Region" type="string" required>
The AWS data center region.
</ParamField>
<ParamField path="IAM User Permission Boundary" type="string" required>
The IAM Policy ARN of the [AWS Permissions Boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to attach to IAM users created in the role.
</ParamField>
<ParamField path="AWS IAM Groups" type="string">
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas
</ParamField>
<ParamField path="AWS Policy ARNs" type="string">
The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas
</ParamField>
<ParamField path="AWS IAM Policy Document" type="string">
The AWS IAM inline policy that should be attached to the created users.
Multiple values can be provided by separating them with commas
</ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
<Tabs>
<Tab title="IAM User">
<ParamField path="AWS IAM Path" type="string">
[IAM AWS Path](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) to scope created IAM User resource access.
</ParamField>
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="IAM User Permission Boundary" type="string">
The IAM Policy ARN of the [AWS Permissions Boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to attach to IAM users created in the role.
</ParamField>
<ParamField path="AWS IAM Groups" type="string">
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas.
</ParamField>
<ParamField path="AWS Policy ARNs" type="string">
The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas.
</ParamField>
<ParamField path="AWS IAM Policy Document" type="string">
The AWS IAM inline policy that should be attached to the created users. Multiple values can be provided by separating them with commas.
</ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are:
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
- `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random N}}`: Random string of N characters
Allowed template functions are:
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
</ParamField>
<ParamField path="Tags" type="map<string, string>[]">
Tags to be added to the created IAM User resource.
</ParamField>
</Tab>
<Tab title="Temporary Credentials">
When **Credential Type** is set to **Temporary Credentials**:
<Info>
No additional configuration parameters are required. The generated credentials will:
- Inherit the permissions of the assumed IRSA role
- Include an AWS Session Token
- Be valid for the duration specified in Default TTL
</Info>
<Note>
**Duration Limit**: IRSA temporary credentials support up to 12 hours maximum via GetSessionToken. TTL values exceeding this limit will be automatically adjusted.
</Note>
</Tab>
</Tabs>
</Step>
<Step title="Click 'Submit'">
After submitting the form, you will see a dynamic secret created in the dashboard.
@@ -429,6 +557,12 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
Select *Access Key* method.
</ParamField>
<ParamField path="Credential Type" type="string" required>
Choose the credential generation approach:
- **IAM User**: Creates new temporary IAM users in your AWS account
- **Temporary Credentials**: Generates temporary credentials from your access key connection
</ParamField>
<ParamField path="AWS Access Key" type="string" required>
The managing AWS IAM User Access Key
</ParamField>
@@ -437,43 +571,66 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
The managing AWS IAM User Secret Key
</ParamField>
<ParamField path="AWS IAM Path" type="string">
[IAM AWS Path](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) to scope created IAM User resource access.
</ParamField>
<ParamField path="AWS Region" type="string" required>
The AWS data center region.
</ParamField>
<ParamField path="IAM User Permission Boundary" type="string" required>
The IAM Policy ARN of the [AWS Permissions Boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to attach to IAM users created in the role.
</ParamField>
<Tabs>
<Tab title="IAM User">
<ParamField path="AWS IAM Path" type="string">
[IAM AWS Path](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) to scope created IAM User resource access.
</ParamField>
<ParamField path="AWS IAM Groups" type="string">
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas
</ParamField>
<ParamField path="IAM User Permission Boundary" type="string">
The IAM Policy ARN of the [AWS Permissions Boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to attach to IAM users created in the role.
</ParamField>
<ParamField path="AWS Policy ARNs" type="string">
The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas
</ParamField>
<ParamField path="AWS IAM Groups" type="string">
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas.
</ParamField>
<ParamField path="AWS IAM Policy Document" type="string">
The AWS IAM inline policy that should be attached to the created users.
Multiple values can be provided by separating them with commas
</ParamField>
<ParamField path="AWS Policy ARNs" type="string">
The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas.
</ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
<ParamField path="AWS IAM Policy Document" type="string">
The AWS IAM inline policy that should be attached to the created users. Multiple values can be provided by separating them with commas.
</ParamField>
Allowed template variables are
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
Allowed template variables are:
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
- `{{identity.name}}`: Name of the identity that is generating the secret
- `{{random N}}`: Random string of N characters
<ParamField path="Tags" type="map[string]string">
Tags to be added to the created IAM User resource.
</ParamField>
Allowed template functions are:
- `truncate`: Truncates a string to a specified length
- `replace`: Replaces a substring with another value
</ParamField>
<ParamField path="Tags" type="map[string]string">
Tags to be added to the created IAM User resource.
</ParamField>
</Tab>
<Tab title="Temporary Credentials">
When **Credential Type** is set to **Temporary Credentials**:
<Info>
No additional configuration parameters are required. The generated credentials will:
- Inherit the permissions of your access key connection
- Include an AWS Session Token
- Be valid for the duration specified in Default TTL
</Info>
<Note>
**Duration Limit**: Access Key temporary credentials support up to 12 hours maximum via GetSessionToken. TTL values exceeding this limit will be automatically adjusted.
</Note>
</Tab>
</Tabs>
</Step>
@@ -500,6 +657,18 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you.
**Credentials format depends on your chosen credential type:**
**IAM User credential type:**
- AWS Username
- AWS Access Key ID
- AWS Secret Access Key
**Temporary Credentials credential type:**
- AWS Access Key ID
- AWS Secret Access Key
- AWS Session Token
![Provision Lease](/images/platform/dynamic-secrets/lease-values-aws-iam.png)
</Step>
</Steps>

View File

@@ -59,6 +59,11 @@ export enum DynamicSecretAwsIamAuth {
IRSA = "irsa"
}
export enum DynamicSecretAwsIamCredentialType {
IamUser = "iam-user",
TemporaryCredentials = "temporary-credentials"
}
export type TDynamicSecretProvider =
| {
type: DynamicSecretProviders.SqlDatabase;
@@ -97,6 +102,7 @@ export type TDynamicSecretProvider =
inputs:
| {
method: DynamicSecretAwsIamAuth.AccessKey;
credentialType: DynamicSecretAwsIamCredentialType;
accessKey: string;
secretAccessKey: string;
region: string;
@@ -107,6 +113,7 @@ export type TDynamicSecretProvider =
}
| {
method: DynamicSecretAwsIamAuth.AssumeRole;
credentialType: DynamicSecretAwsIamCredentialType;
roleArn: string;
region: string;
awsPath?: string;
@@ -116,6 +123,7 @@ export type TDynamicSecretProvider =
}
| {
method: DynamicSecretAwsIamAuth.IRSA;
credentialType: DynamicSecretAwsIamCredentialType;
region: string;
awsPath?: string;
policyDocument?: string;

View File

@@ -18,6 +18,7 @@ import { useCreateDynamicSecret } from "@app/hooks/api";
import { useGetServerConfig } from "@app/hooks/api/admin";
import {
DynamicSecretAwsIamAuth,
DynamicSecretAwsIamCredentialType,
DynamicSecretProviders
} from "@app/hooks/api/dynamicSecret/types";
import { ProjectEnv } from "@app/hooks/api/types";
@@ -28,6 +29,9 @@ const formSchema = z.object({
provider: z.discriminatedUnion("method", [
z.object({
method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
accessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1),
region: z.string().trim().min(1),
@@ -47,6 +51,9 @@ const formSchema = z.object({
}),
z.object({
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
roleArn: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
@@ -65,6 +72,9 @@ const formSchema = z.object({
}),
z.object({
method: z.literal(DynamicSecretAwsIamAuth.IRSA),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
@@ -137,13 +147,15 @@ export const AwsIamInputForm = ({
environment: isSingleEnvironmentMode ? environments[0] : undefined,
usernameTemplate: "{{randomUsername}}",
provider: {
method: DynamicSecretAwsIamAuth.AssumeRole
method: DynamicSecretAwsIamAuth.AssumeRole,
credentialType: DynamicSecretAwsIamCredentialType.IamUser
}
}
});
const createDynamicSecret = useCreateDynamicSecret();
const method = watch("provider.method");
const credentialType = watch("provider.credentialType");
const handleCreateDynamicSecret = async ({
name,
@@ -264,6 +276,39 @@ export const AwsIamInputForm = ({
</FormControl>
)}
/>
<Controller
name="provider.credentialType"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Credential Type"
>
<>
<Select
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
<SelectItem value={DynamicSecretAwsIamCredentialType.IamUser}>
IAM User
</SelectItem>
<SelectItem value={DynamicSecretAwsIamCredentialType.TemporaryCredentials}>
Temporary Credentials
</SelectItem>
</Select>
<div className="mt-1 text-xs text-mineshaft-300">
{value === DynamicSecretAwsIamCredentialType.IamUser
? "Creates temporary IAM users with access keys"
: "Uses STS to generate temporary credentials from your connection. Duration is controlled by the Default TTL setting above."}
</div>
</>
</FormControl>
)}
/>
{method === DynamicSecretAwsIamAuth.AccessKey && (
<div className="flex items-center space-x-2">
<Controller
@@ -318,22 +363,24 @@ export const AwsIamInputForm = ({
</div>
)}
<div className="flex items-center space-x-2">
<Controller
control={control}
name="provider.awsPath"
defaultValue="/"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Path"
className="flex-grow"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<Controller
control={control}
name="provider.awsPath"
defaultValue="/"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Path"
className="flex-grow"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
)}
<Controller
control={control}
name="provider.region"
@@ -341,7 +388,11 @@ export const AwsIamInputForm = ({
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS Region"
className="flex-grow"
className={
credentialType === DynamicSecretAwsIamCredentialType.TemporaryCredentials
? "w-full"
: "flex-grow"
}
isError={Boolean(error?.message)}
errorText={error?.message}
>
@@ -350,97 +401,105 @@ export const AwsIamInputForm = ({
)}
/>
</div>
<Controller
control={control}
name="provider.permissionBoundaryPolicyArn"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="IAM User Permission Boundary ARN"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.userGroups"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Groups"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given groups."
>
<Input {...field} placeholder="group1,group2" />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.policyArns"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS Policy ARNs"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given policy arns."
>
<Input
{...field}
placeholder="arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.policyDocument"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Policy Document"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="Generated users will have the inline policy."
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
<MetadataForm control={control} name="provider.tags" title="Tags" isValueRequired />
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<>
<Controller
control={control}
name="provider.permissionBoundaryPolicyArn"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="IAM User Permission Boundary ARN"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.userGroups"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Groups"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given groups."
>
<Input {...field} placeholder="group1,group2" />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.policyArns"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS Policy ARNs"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given policy arns."
>
<Input
{...field}
placeholder="arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.policyDocument"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Policy Document"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="Generated users will have the inline policy."
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</>
)}
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
placeholder="{{randomUsername}}"
/>
</FormControl>
)}
/>
)}
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<MetadataForm control={control} name="provider.tags" title="Tags" isValueRequired />
)}
{!isSingleEnvironmentMode && (
<Controller
control={control}

View File

@@ -154,20 +154,22 @@ const renderOutputForm = (
}
if (provider === DynamicSecretProviders.AwsIam) {
const { USERNAME, ACCESS_KEY, SECRET_ACCESS_KEY } = data as {
const { USERNAME, ACCESS_KEY, SECRET_ACCESS_KEY, SESSION_TOKEN } = data as {
ACCESS_KEY: string;
SECRET_ACCESS_KEY: string;
USERNAME: string;
USERNAME?: string;
SESSION_TOKEN?: string;
};
return (
<div>
<OutputDisplay label="AWS Username" value={USERNAME} />
{USERNAME && <OutputDisplay label="AWS IAM Username" value={USERNAME} />}
<OutputDisplay label="AWS IAM Access Key" value={ACCESS_KEY} />
<OutputDisplay
label="AWS IAM Secret Key"
value={SECRET_ACCESS_KEY}
helperText="Important: Copy these credentials now. You will not be able to see them again after you close the modal."
/>
<OutputDisplay label="AWS IAM Secret Key" value={SECRET_ACCESS_KEY} />
{SESSION_TOKEN && <OutputDisplay label="AWS IAM Session Token" value={SESSION_TOKEN} />}
<div className="mt-2 text-xs text-mineshaft-300">
Important: Copy these credentials now. You will not be able to see them again after you
close the modal.
</div>
</div>
);
}

View File

@@ -7,7 +7,11 @@ import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2";
import { useGetServerConfig, useUpdateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretAwsIamAuth, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
import {
DynamicSecretAwsIamAuth,
DynamicSecretAwsIamCredentialType,
TDynamicSecret
} from "@app/hooks/api/dynamicSecret/types";
import { slugSchema } from "@app/lib/schemas";
import { MetadataForm } from "../MetadataForm";
@@ -16,6 +20,9 @@ const formSchema = z.object({
inputs: z.discriminatedUnion("method", [
z.object({
method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
accessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1),
region: z.string().trim().min(1),
@@ -30,6 +37,9 @@ const formSchema = z.object({
}),
z.object({
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
roleArn: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
@@ -43,6 +53,9 @@ const formSchema = z.object({
}),
z.object({
method: z.literal(DynamicSecretAwsIamAuth.IRSA),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
@@ -78,6 +91,7 @@ const formSchema = z.object({
newName: slugSchema().optional(),
usernameTemplate: z.string().trim().nullable().optional()
});
type TForm = z.infer<typeof formSchema>;
type Props = {
@@ -115,6 +129,7 @@ export const EditDynamicSecretAwsIamForm = ({
}
});
const method = watch("inputs.method");
const credentialType = watch("inputs.credentialType");
const updateDynamicSecret = useUpdateDynamicSecret();
@@ -235,6 +250,39 @@ export const EditDynamicSecretAwsIamForm = ({
</FormControl>
)}
/>
<Controller
name="inputs.credentialType"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Credential Type"
>
<>
<Select
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
<SelectItem value={DynamicSecretAwsIamCredentialType.IamUser}>
IAM User
</SelectItem>
<SelectItem value={DynamicSecretAwsIamCredentialType.TemporaryCredentials}>
Temporary Credentials
</SelectItem>
</Select>
<div className="mt-1 text-xs text-mineshaft-300">
{value === DynamicSecretAwsIamCredentialType.IamUser
? "Creates temporary IAM users with access keys"
: "Uses STS to generate temporary credentials from your connection. Duration is controlled by the Default TTL setting above."}
</div>
</>
</FormControl>
)}
/>
{method === DynamicSecretAwsIamAuth.AccessKey && (
<div className="flex items-center space-x-2">
<Controller
@@ -289,21 +337,24 @@ export const EditDynamicSecretAwsIamForm = ({
</div>
)}
<div className="flex items-center space-x-2">
<Controller
control={control}
name="inputs.awsPath"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Path"
className="flex-grow"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<Controller
control={control}
name="inputs.awsPath"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Path"
className="flex-grow"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
)}
<Controller
control={control}
name="inputs.region"
@@ -311,7 +362,11 @@ export const EditDynamicSecretAwsIamForm = ({
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS Region"
className="flex-grow"
className={
credentialType === DynamicSecretAwsIamCredentialType.TemporaryCredentials
? "w-full"
: "flex-grow"
}
isError={Boolean(error?.message)}
errorText={error?.message}
>
@@ -320,93 +375,101 @@ export const EditDynamicSecretAwsIamForm = ({
)}
/>
</div>
<Controller
control={control}
name="inputs.userGroups"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Groups"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given groups."
>
<Input {...field} placeholder="group1,group2" />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.permissionBoundaryPolicyArn"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="IAM User Permission Boundary ARN"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.policyArns"
defaultValue="datacenter1"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS Policy ARNs"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given policy arns."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.policyDocument"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Policy Document"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="Generated users will have the inline policy."
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<MetadataForm control={control} name="inputs.tags" title="Tags" isValueRequired />
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<>
<Controller
control={control}
name="inputs.userGroups"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Groups"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given groups."
>
<Input {...field} placeholder="group1,group2" />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.permissionBoundaryPolicyArn"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="IAM User Permission Boundary ARN"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.policyArns"
defaultValue="datacenter1"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS Policy ARNs"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given policy arns."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.policyDocument"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Policy Document"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="Generated users will have the inline policy."
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</>
)}
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
)}
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<MetadataForm control={control} name="inputs.tags" title="Tags" isValueRequired />
)}
</div>
</div>
<div className="mt-4 flex items-center space-x-4">