mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 02:27:39 +00:00
Dynamic Secrets: add Temporary Credentials for AWS IAM Roles
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
|||||||
PutUserPolicyCommand,
|
PutUserPolicyCommand,
|
||||||
RemoveUserFromGroupCommand
|
RemoveUserFromGroupCommand
|
||||||
} from "@aws-sdk/client-iam";
|
} 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 { z } from "zod";
|
||||||
|
|
||||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||||
@@ -26,9 +26,14 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
|||||||
import { sanitizeString } from "@app/lib/fn";
|
import { sanitizeString } from "@app/lib/fn";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
import { AwsIamAuthType, DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
|
import { AwsIamAuthType, AwsIamCredentialType, DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
|
||||||
import { compileUsernameTemplate } from "./templateUtils";
|
import { compileUsernameTemplate } from "./templateUtils";
|
||||||
|
|
||||||
|
// AWS STS duration constants (in seconds)
|
||||||
|
const AWS_STS_MIN_DURATION = 900; // 15 minutes
|
||||||
|
const AWS_STS_MAX_DURATION_SESSION_TOKEN = 43200; // 12 hours for GetSessionToken
|
||||||
|
const AWS_STS_MAX_DURATION_ASSUME_ROLE = 43200; // 12 hours for AssumeRole
|
||||||
|
|
||||||
const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => {
|
const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => {
|
||||||
const randomUsername = alphaNumericNanoId(32);
|
const randomUsername = alphaNumericNanoId(32);
|
||||||
if (!usernameTemplate) return randomUsername;
|
if (!usernameTemplate) return randomUsername;
|
||||||
@@ -120,6 +125,58 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
|
|||||||
const validateConnection = async (inputs: unknown, { projectId }: { projectId: string }) => {
|
const validateConnection = async (inputs: unknown, { projectId }: { projectId: string }) => {
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
try {
|
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 client = await $getClient(providerInputs, projectId);
|
||||||
const isConnected = await client
|
const isConnected = await client
|
||||||
.send(new GetUserCommand({}))
|
.send(new GetUserCommand({}))
|
||||||
@@ -137,13 +194,21 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
|
|||||||
});
|
});
|
||||||
return isConnected;
|
return isConnected;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const sensitiveTokens = [];
|
const sensitiveTokens: string[] = [];
|
||||||
if (providerInputs.method === AwsIamAuthType.AccessKey) {
|
if (providerInputs.method === AwsIamAuthType.AccessKey) {
|
||||||
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
|
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
|
||||||
}
|
}
|
||||||
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
|
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
|
||||||
sensitiveTokens.push(providerInputs.roleArn);
|
sensitiveTokens.push(providerInputs.roleArn);
|
||||||
}
|
}
|
||||||
|
if (providerInputs.credentialType === AwsIamCredentialType.TemporaryCredentials) {
|
||||||
|
if (providerInputs.method === AwsIamAuthType.AccessKey) {
|
||||||
|
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
|
||||||
|
}
|
||||||
|
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
|
||||||
|
sensitiveTokens.push(providerInputs.roleArn);
|
||||||
|
}
|
||||||
|
}
|
||||||
const sanitizedErrorMessage = sanitizeString({
|
const sanitizedErrorMessage = sanitizeString({
|
||||||
unsanitizedString: (err as Error)?.message,
|
unsanitizedString: (err as Error)?.message,
|
||||||
tokens: sensitiveTokens
|
tokens: sensitiveTokens
|
||||||
@@ -163,102 +228,258 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
|
|||||||
};
|
};
|
||||||
metadata: { projectId: string };
|
metadata: { projectId: string };
|
||||||
}) => {
|
}) => {
|
||||||
const { inputs, usernameTemplate, metadata, identity } = data;
|
const { inputs, usernameTemplate, metadata, identity, expireAt } = data;
|
||||||
|
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
const client = await $getClient(providerInputs, metadata.projectId);
|
|
||||||
|
|
||||||
const username = generateUsername(usernameTemplate, identity);
|
if (providerInputs.credentialType === AwsIamCredentialType.TemporaryCredentials) {
|
||||||
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
|
try {
|
||||||
const awsTags = [{ Key: "createdBy", Value: "infisical-dynamic-secret" }];
|
let stsClient: STSClient;
|
||||||
|
let entityId: string;
|
||||||
|
|
||||||
if (providerInputs.tags && Array.isArray(providerInputs.tags)) {
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
const additionalTags = providerInputs.tags.map((tag) => ({
|
const requestedDuration = expireAt - currentTime;
|
||||||
Key: tag.key,
|
|
||||||
Value: tag.value
|
if (requestedDuration <= 0) {
|
||||||
}));
|
throw new BadRequestError({ message: "Expiration time must be in the future" });
|
||||||
awsTags.push(...additionalTags);
|
}
|
||||||
|
|
||||||
|
let durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_SESSION_TOKEN);
|
||||||
|
|
||||||
|
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
durationSeconds = Math.min(durationSeconds, AWS_STS_MAX_DURATION_ASSUME_ROLE);
|
||||||
|
|
||||||
|
const assumeRoleRes = await stsClient.send(
|
||||||
|
new AssumeRoleCommand({
|
||||||
|
RoleArn: providerInputs.roleArn,
|
||||||
|
RoleSessionName: `infisical-temp-cred-${crypto.nativeCrypto.randomUUID()}`,
|
||||||
|
DurationSeconds: Math.max(durationSeconds, AWS_STS_MIN_DURATION),
|
||||||
|
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) {
|
||||||
|
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: Math.max(durationSeconds, AWS_STS_MIN_DURATION)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
stsClient = new STSClient({
|
||||||
|
region: providerInputs.region,
|
||||||
|
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||||
|
sha256: CustomAWSHasher
|
||||||
|
});
|
||||||
|
|
||||||
|
const sessionTokenRes = await stsClient.send(
|
||||||
|
new GetSessionTokenCommand({
|
||||||
|
DurationSeconds: Math.max(durationSeconds, AWS_STS_MIN_DURATION)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
const sanitizedErrorMessage = sanitizeString({
|
||||||
|
unsanitizedString: (err as Error)?.message,
|
||||||
|
tokens: sensitiveTokens
|
||||||
|
});
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to create temporary credentials: ${sanitizedErrorMessage}`
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (providerInputs.credentialType === AwsIamCredentialType.IamUser) {
|
||||||
const createUserRes = await client.send(
|
const client = await $getClient(providerInputs, metadata.projectId);
|
||||||
new CreateUserCommand({
|
|
||||||
Path: awsPath,
|
|
||||||
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
|
|
||||||
Tags: awsTags,
|
|
||||||
UserName: username
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!createUserRes.User) throw new BadRequestError({ message: "Failed to create AWS IAM User" });
|
const username = generateUsername(usernameTemplate, identity);
|
||||||
if (userGroups) {
|
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
|
||||||
await Promise.all(
|
const awsTags = [{ Key: "createdBy", Value: "infisical-dynamic-secret" }];
|
||||||
userGroups
|
|
||||||
.split(",")
|
if (providerInputs.tags && Array.isArray(providerInputs.tags)) {
|
||||||
.filter(Boolean)
|
const additionalTags = providerInputs.tags.map((tag) => ({
|
||||||
.map((group) =>
|
Key: tag.key,
|
||||||
client.send(new AddUserToGroupCommand({ UserName: createUserRes?.User?.UserName, GroupName: group }))
|
Value: tag.value
|
||||||
)
|
}));
|
||||||
);
|
awsTags.push(...additionalTags);
|
||||||
}
|
}
|
||||||
if (policyArns) {
|
|
||||||
await Promise.all(
|
try {
|
||||||
policyArns
|
const createUserRes = await client.send(
|
||||||
.split(",")
|
new CreateUserCommand({
|
||||||
.filter(Boolean)
|
Path: awsPath,
|
||||||
.map((policyArn) =>
|
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
|
||||||
client.send(
|
Tags: awsTags,
|
||||||
new AttachUserPolicyCommand({ UserName: createUserRes?.User?.UserName, PolicyArn: policyArn })
|
UserName: username
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (policyDocument) {
|
|
||||||
await client.send(
|
|
||||||
new PutUserPolicyCommand({
|
|
||||||
UserName: createUserRes.User.UserName,
|
|
||||||
PolicyName: `infisical-dynamic-policy-${alphaNumericNanoId(4)}`,
|
|
||||||
PolicyDocument: policyDocument
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
const createAccessKeyRes = await client.send(
|
if (!createUserRes.User) throw new BadRequestError({ message: "Failed to create AWS IAM User" });
|
||||||
new CreateAccessKeyCommand({
|
if (userGroups) {
|
||||||
UserName: createUserRes.User.UserName
|
await Promise.all(
|
||||||
})
|
userGroups
|
||||||
);
|
.split(",")
|
||||||
if (!createAccessKeyRes.AccessKey)
|
.filter(Boolean)
|
||||||
throw new BadRequestError({ message: "Failed to create AWS IAM User access key" });
|
.map((group) =>
|
||||||
|
client.send(new AddUserToGroupCommand({ UserName: createUserRes?.User?.UserName, GroupName: group }))
|
||||||
return {
|
)
|
||||||
entityId: username,
|
);
|
||||||
data: {
|
|
||||||
ACCESS_KEY: createAccessKeyRes.AccessKey.AccessKeyId,
|
|
||||||
SECRET_ACCESS_KEY: createAccessKeyRes.AccessKey.SecretAccessKey,
|
|
||||||
USERNAME: username
|
|
||||||
}
|
}
|
||||||
};
|
if (policyArns) {
|
||||||
} catch (err) {
|
await Promise.all(
|
||||||
const sensitiveTokens = [username];
|
policyArns
|
||||||
if (providerInputs.method === AwsIamAuthType.AccessKey) {
|
.split(",")
|
||||||
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
|
.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 revoke = async (inputs: unknown, entityId: string, metadata: { projectId: string }) => {
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
|
if (providerInputs.credentialType === AwsIamCredentialType.TemporaryCredentials) {
|
||||||
|
return { entityId };
|
||||||
|
}
|
||||||
|
|
||||||
const client = await $getClient(providerInputs, metadata.projectId);
|
const client = await $getClient(providerInputs, metadata.projectId);
|
||||||
|
|
||||||
const username = entityId;
|
const username = entityId;
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ export enum AwsIamAuthType {
|
|||||||
IRSA = "irsa"
|
IRSA = "irsa"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum AwsIamCredentialType {
|
||||||
|
IamUser = "iam-user",
|
||||||
|
TemporaryCredentials = "temporary-credentials"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ElasticSearchAuthTypes {
|
export enum ElasticSearchAuthTypes {
|
||||||
User = "user",
|
User = "user",
|
||||||
ApiKey = "api-key"
|
ApiKey = "api-key"
|
||||||
@@ -202,6 +207,7 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
|
|||||||
z.discriminatedUnion("method", [
|
z.discriminatedUnion("method", [
|
||||||
z.object({
|
z.object({
|
||||||
method: z.literal(AwsIamAuthType.AccessKey),
|
method: z.literal(AwsIamAuthType.AccessKey),
|
||||||
|
credentialType: z.nativeEnum(AwsIamCredentialType).default(AwsIamCredentialType.IamUser),
|
||||||
accessKey: z.string().trim().min(1),
|
accessKey: z.string().trim().min(1),
|
||||||
secretAccessKey: z.string().trim().min(1),
|
secretAccessKey: z.string().trim().min(1),
|
||||||
region: z.string().trim().min(1),
|
region: z.string().trim().min(1),
|
||||||
@@ -214,6 +220,7 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
method: z.literal(AwsIamAuthType.AssumeRole),
|
method: z.literal(AwsIamAuthType.AssumeRole),
|
||||||
|
credentialType: z.nativeEnum(AwsIamCredentialType).default(AwsIamCredentialType.IamUser),
|
||||||
roleArn: z.string().trim().min(1, "Role ARN required"),
|
roleArn: z.string().trim().min(1, "Role ARN required"),
|
||||||
region: z.string().trim().min(1),
|
region: z.string().trim().min(1),
|
||||||
awsPath: z.string().trim().optional(),
|
awsPath: z.string().trim().optional(),
|
||||||
@@ -225,6 +232,7 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
method: z.literal(AwsIamAuthType.IRSA),
|
method: z.literal(AwsIamAuthType.IRSA),
|
||||||
|
credentialType: z.nativeEnum(AwsIamCredentialType).default(AwsIamCredentialType.IamUser),
|
||||||
region: z.string().trim().min(1),
|
region: z.string().trim().min(1),
|
||||||
awsPath: z.string().trim().optional(),
|
awsPath: z.string().trim().optional(),
|
||||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
|
|||||||
@@ -3,49 +3,82 @@ title: "AWS IAM"
|
|||||||
description: "Learn how to dynamically generate AWS IAM Users."
|
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.
|
||||||
|
|
||||||
## Prerequisite
|
## 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">
|
<Accordion title="Required IAM Permissions">
|
||||||
|
|
||||||
```json
|
<Tabs>
|
||||||
{
|
<Tab title="IAM User">
|
||||||
"Version": "2012-10-17",
|
Required permissions for creating temporary IAM users:
|
||||||
"Statement": [
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
"Effect": "Allow",
|
"Version": "2012-10-17",
|
||||||
"Action": [
|
"Statement": [
|
||||||
"iam:AttachUserPolicy",
|
{
|
||||||
"iam:CreateAccessKey",
|
"Effect": "Allow",
|
||||||
"iam:CreateUser",
|
"Action": [
|
||||||
"iam:DeleteAccessKey",
|
"iam:AttachUserPolicy",
|
||||||
"iam:DeleteUser",
|
"iam:CreateAccessKey",
|
||||||
"iam:DeleteUserPolicy",
|
"iam:CreateUser",
|
||||||
"iam:DetachUserPolicy",
|
"iam:DeleteAccessKey",
|
||||||
"iam:GetUser",
|
"iam:DeleteUser",
|
||||||
"iam:ListAccessKeys",
|
"iam:DeleteUserPolicy",
|
||||||
"iam:ListAttachedUserPolicies",
|
"iam:DetachUserPolicy",
|
||||||
"iam:ListGroupsForUser",
|
"iam:GetUser",
|
||||||
"iam:ListUserPolicies",
|
"iam:ListAccessKeys",
|
||||||
"iam:PutUserPolicy",
|
"iam:ListAttachedUserPolicies",
|
||||||
"iam:AddUserToGroup",
|
"iam:ListGroupsForUser",
|
||||||
"iam:RemoveUserFromGroup",
|
"iam:ListUserPolicies",
|
||||||
"iam:TagUser"
|
"iam:PutUserPolicy",
|
||||||
],
|
"iam:AddUserToGroup",
|
||||||
"Resource": ["*"]
|
"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>
|
</Accordion>
|
||||||
|
|
||||||
@@ -170,43 +203,72 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
|
|||||||
Select *Assume Role* method.
|
Select *Assume Role* method.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="Aws Role ARN" type="string" required>
|
<ParamField path="Credential Type" type="string" required>
|
||||||
The ARN of the AWS Role to assume.
|
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>
|
||||||
|
|
||||||
<ParamField path="AWS IAM Path" type="string">
|
<ParamField path="Aws Role ARN" type="string" required>
|
||||||
[IAM AWS Path](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) to scope created IAM User resource access.
|
The ARN of the AWS Role to assume.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="AWS Region" type="string" required>
|
<ParamField path="AWS Region" type="string" required>
|
||||||
The AWS data center region.
|
The AWS data center region.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="IAM User Permission Boundary" type="string" required>
|
<Tabs>
|
||||||
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.
|
<Tab title="IAM User">
|
||||||
</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 IAM Groups" type="string">
|
<ParamField path="IAM User Permission Boundary" type="string">
|
||||||
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas
|
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>
|
||||||
|
|
||||||
<ParamField path="AWS Policy ARNs" type="string">
|
<ParamField path="AWS IAM Groups" 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
|
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="AWS IAM Policy Document" type="string">
|
<ParamField path="AWS Policy ARNs" type="string">
|
||||||
The AWS IAM inline policy that should be attached to the created users.
|
The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas.
|
||||||
Multiple values can be provided by separating them with commas
|
</ParamField>
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
|
<ParamField path="AWS IAM Policy Document" type="string">
|
||||||
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
|
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
|
Allowed template variables are:
|
||||||
- `{{unixTimestamp}}`: Current Unix timestamp
|
- `{{randomUsername}}`: Random username string
|
||||||
</ParamField>
|
- `{{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>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step title="Click 'Submit'">
|
<Step title="Click 'Submit'">
|
||||||
@@ -232,6 +294,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.
|
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
|
||||||
|
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -342,36 +416,71 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
|
|||||||
<ParamField path="Method" type="string" required>
|
<ParamField path="Method" type="string" required>
|
||||||
Select *IRSA* method.
|
Select *IRSA* method.
|
||||||
</ParamField>
|
</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>
|
<ParamField path="Aws Role ARN" type="string" required>
|
||||||
The ARN of the AWS IAM Role for the service account to assume.
|
The ARN of the AWS IAM Role for the service account to assume.
|
||||||
</ParamField>
|
</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>
|
<ParamField path="AWS Region" type="string" required>
|
||||||
The AWS data center region.
|
The AWS data center region.
|
||||||
</ParamField>
|
</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
|
<ParamField path="IAM User Permission Boundary" type="string">
|
||||||
- `{{unixTimestamp}}`: Current Unix timestamp
|
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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Click 'Submit'">
|
<Step title="Click 'Submit'">
|
||||||
After submitting the form, you will see a dynamic secret created in the dashboard.
|
After submitting the form, you will see a dynamic secret created in the dashboard.
|
||||||
@@ -429,6 +538,12 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
|
|||||||
Select *Access Key* method.
|
Select *Access Key* method.
|
||||||
</ParamField>
|
</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>
|
<ParamField path="AWS Access Key" type="string" required>
|
||||||
The managing AWS IAM User Access Key
|
The managing AWS IAM User Access Key
|
||||||
</ParamField>
|
</ParamField>
|
||||||
@@ -437,43 +552,62 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
|
|||||||
The managing AWS IAM User Secret Key
|
The managing AWS IAM User Secret Key
|
||||||
</ParamField>
|
</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>
|
<ParamField path="AWS Region" type="string" required>
|
||||||
The AWS data center region.
|
The AWS data center region.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="IAM User Permission Boundary" type="string" required>
|
<Tabs>
|
||||||
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.
|
<Tab title="IAM User">
|
||||||
</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 IAM Groups" type="string">
|
<ParamField path="IAM User Permission Boundary" type="string">
|
||||||
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas
|
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>
|
||||||
|
|
||||||
<ParamField path="AWS Policy ARNs" type="string">
|
<ParamField path="AWS IAM Groups" 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
|
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="AWS IAM Policy Document" type="string">
|
<ParamField path="AWS Policy ARNs" type="string">
|
||||||
The AWS IAM inline policy that should be attached to the created users.
|
The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas.
|
||||||
Multiple values can be provided by separating them with commas
|
</ParamField>
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
|
<ParamField path="AWS IAM Policy Document" type="string">
|
||||||
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
|
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
|
Allowed template variables are:
|
||||||
- `{{unixTimestamp}}`: Current Unix timestamp
|
- `{{randomUsername}}`: Random username string
|
||||||
</ParamField>
|
- `{{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">
|
Allowed template functions are:
|
||||||
Tags to be added to the created IAM User resource.
|
- `truncate`: Truncates a string to a specified length
|
||||||
</ParamField>
|
- `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>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
@@ -500,6 +634,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.
|
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
|
||||||
|
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ export enum DynamicSecretAwsIamAuth {
|
|||||||
IRSA = "irsa"
|
IRSA = "irsa"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum DynamicSecretAwsIamCredentialType {
|
||||||
|
IamUser = "iam-user",
|
||||||
|
TemporaryCredentials = "temporary-credentials"
|
||||||
|
}
|
||||||
|
|
||||||
export type TDynamicSecretProvider =
|
export type TDynamicSecretProvider =
|
||||||
| {
|
| {
|
||||||
type: DynamicSecretProviders.SqlDatabase;
|
type: DynamicSecretProviders.SqlDatabase;
|
||||||
@@ -97,6 +102,7 @@ export type TDynamicSecretProvider =
|
|||||||
inputs:
|
inputs:
|
||||||
| {
|
| {
|
||||||
method: DynamicSecretAwsIamAuth.AccessKey;
|
method: DynamicSecretAwsIamAuth.AccessKey;
|
||||||
|
credentialType?: DynamicSecretAwsIamCredentialType;
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
region: string;
|
region: string;
|
||||||
@@ -107,6 +113,7 @@ export type TDynamicSecretProvider =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
method: DynamicSecretAwsIamAuth.AssumeRole;
|
method: DynamicSecretAwsIamAuth.AssumeRole;
|
||||||
|
credentialType?: DynamicSecretAwsIamCredentialType;
|
||||||
roleArn: string;
|
roleArn: string;
|
||||||
region: string;
|
region: string;
|
||||||
awsPath?: string;
|
awsPath?: string;
|
||||||
@@ -116,6 +123,7 @@ export type TDynamicSecretProvider =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
method: DynamicSecretAwsIamAuth.IRSA;
|
method: DynamicSecretAwsIamAuth.IRSA;
|
||||||
|
credentialType?: DynamicSecretAwsIamCredentialType;
|
||||||
region: string;
|
region: string;
|
||||||
awsPath?: string;
|
awsPath?: string;
|
||||||
policyDocument?: string;
|
policyDocument?: string;
|
||||||
|
|||||||
+168
-109
@@ -18,6 +18,7 @@ import { useCreateDynamicSecret } from "@app/hooks/api";
|
|||||||
import { useGetServerConfig } from "@app/hooks/api/admin";
|
import { useGetServerConfig } from "@app/hooks/api/admin";
|
||||||
import {
|
import {
|
||||||
DynamicSecretAwsIamAuth,
|
DynamicSecretAwsIamAuth,
|
||||||
|
DynamicSecretAwsIamCredentialType,
|
||||||
DynamicSecretProviders
|
DynamicSecretProviders
|
||||||
} from "@app/hooks/api/dynamicSecret/types";
|
} from "@app/hooks/api/dynamicSecret/types";
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/types";
|
import { WorkspaceEnv } from "@app/hooks/api/types";
|
||||||
@@ -28,6 +29,9 @@ const formSchema = z.object({
|
|||||||
provider: z.discriminatedUnion("method", [
|
provider: z.discriminatedUnion("method", [
|
||||||
z.object({
|
z.object({
|
||||||
method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
|
method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
|
||||||
|
credentialType: z
|
||||||
|
.nativeEnum(DynamicSecretAwsIamCredentialType)
|
||||||
|
.default(DynamicSecretAwsIamCredentialType.IamUser),
|
||||||
accessKey: z.string().trim().min(1),
|
accessKey: z.string().trim().min(1),
|
||||||
secretAccessKey: z.string().trim().min(1),
|
secretAccessKey: z.string().trim().min(1),
|
||||||
region: z.string().trim().min(1),
|
region: z.string().trim().min(1),
|
||||||
@@ -47,6 +51,9 @@ const formSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
|
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
|
||||||
|
credentialType: z
|
||||||
|
.nativeEnum(DynamicSecretAwsIamCredentialType)
|
||||||
|
.default(DynamicSecretAwsIamCredentialType.IamUser),
|
||||||
roleArn: z.string().trim().min(1),
|
roleArn: z.string().trim().min(1),
|
||||||
region: z.string().trim().min(1),
|
region: z.string().trim().min(1),
|
||||||
awsPath: z.string().trim().optional(),
|
awsPath: z.string().trim().optional(),
|
||||||
@@ -65,6 +72,9 @@ const formSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
method: z.literal(DynamicSecretAwsIamAuth.IRSA),
|
method: z.literal(DynamicSecretAwsIamAuth.IRSA),
|
||||||
|
credentialType: z
|
||||||
|
.nativeEnum(DynamicSecretAwsIamCredentialType)
|
||||||
|
.default(DynamicSecretAwsIamCredentialType.IamUser),
|
||||||
region: z.string().trim().min(1),
|
region: z.string().trim().min(1),
|
||||||
awsPath: z.string().trim().optional(),
|
awsPath: z.string().trim().optional(),
|
||||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
@@ -137,13 +147,15 @@ export const AwsIamInputForm = ({
|
|||||||
environment: isSingleEnvironmentMode ? environments[0] : undefined,
|
environment: isSingleEnvironmentMode ? environments[0] : undefined,
|
||||||
usernameTemplate: "{{randomUsername}}",
|
usernameTemplate: "{{randomUsername}}",
|
||||||
provider: {
|
provider: {
|
||||||
method: DynamicSecretAwsIamAuth.AssumeRole
|
method: DynamicSecretAwsIamAuth.AssumeRole,
|
||||||
|
credentialType: DynamicSecretAwsIamCredentialType.IamUser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const createDynamicSecret = useCreateDynamicSecret();
|
const createDynamicSecret = useCreateDynamicSecret();
|
||||||
const method = watch("provider.method");
|
const method = watch("provider.method");
|
||||||
|
const credentialType = watch("provider.credentialType");
|
||||||
|
|
||||||
const handleCreateDynamicSecret = async ({
|
const handleCreateDynamicSecret = async ({
|
||||||
name,
|
name,
|
||||||
@@ -264,6 +276,39 @@ export const AwsIamInputForm = ({
|
|||||||
</FormControl>
|
</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 && (
|
{method === DynamicSecretAwsIamAuth.AccessKey && (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Controller
|
<Controller
|
||||||
@@ -318,22 +363,24 @@ export const AwsIamInputForm = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Controller
|
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
|
||||||
control={control}
|
<Controller
|
||||||
name="provider.awsPath"
|
control={control}
|
||||||
defaultValue="/"
|
name="provider.awsPath"
|
||||||
render={({ field, fieldState: { error } }) => (
|
defaultValue="/"
|
||||||
<FormControl
|
render={({ field, fieldState: { error } }) => (
|
||||||
label="AWS IAM Path"
|
<FormControl
|
||||||
className="flex-grow"
|
label="AWS IAM Path"
|
||||||
isOptional
|
className="flex-grow"
|
||||||
isError={Boolean(error?.message)}
|
isOptional
|
||||||
errorText={error?.message}
|
isError={Boolean(error?.message)}
|
||||||
>
|
errorText={error?.message}
|
||||||
<Input {...field} />
|
>
|
||||||
</FormControl>
|
<Input {...field} />
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.region"
|
name="provider.region"
|
||||||
@@ -341,7 +388,11 @@ export const AwsIamInputForm = ({
|
|||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="AWS Region"
|
label="AWS Region"
|
||||||
className="flex-grow"
|
className={
|
||||||
|
credentialType === DynamicSecretAwsIamCredentialType.TemporaryCredentials
|
||||||
|
? "w-full"
|
||||||
|
: "flex-grow"
|
||||||
|
}
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
>
|
>
|
||||||
@@ -350,97 +401,105 @@ export const AwsIamInputForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Controller
|
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
|
||||||
control={control}
|
<>
|
||||||
name="provider.permissionBoundaryPolicyArn"
|
<Controller
|
||||||
defaultValue=""
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="provider.permissionBoundaryPolicyArn"
|
||||||
<FormControl
|
defaultValue=""
|
||||||
label="IAM User Permission Boundary ARN"
|
render={({ field, fieldState: { error } }) => (
|
||||||
isError={Boolean(error?.message)}
|
<FormControl
|
||||||
isOptional
|
label="IAM User Permission Boundary ARN"
|
||||||
errorText={error?.message}
|
isError={Boolean(error?.message)}
|
||||||
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
|
isOptional
|
||||||
>
|
errorText={error?.message}
|
||||||
<Input {...field} />
|
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
|
||||||
</FormControl>
|
>
|
||||||
)}
|
<Input {...field} />
|
||||||
/>
|
</FormControl>
|
||||||
<Controller
|
)}
|
||||||
control={control}
|
/>
|
||||||
name="provider.userGroups"
|
<Controller
|
||||||
defaultValue=""
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="provider.userGroups"
|
||||||
<FormControl
|
defaultValue=""
|
||||||
label="AWS IAM Groups"
|
render={({ field, fieldState: { error } }) => (
|
||||||
isError={Boolean(error?.message)}
|
<FormControl
|
||||||
isOptional
|
label="AWS IAM Groups"
|
||||||
errorText={error?.message}
|
isError={Boolean(error?.message)}
|
||||||
helperText="Generated users will get attached to given groups."
|
isOptional
|
||||||
>
|
errorText={error?.message}
|
||||||
<Input {...field} placeholder="group1,group2" />
|
helperText="Generated users will get attached to given groups."
|
||||||
</FormControl>
|
>
|
||||||
)}
|
<Input {...field} placeholder="group1,group2" />
|
||||||
/>
|
</FormControl>
|
||||||
<Controller
|
)}
|
||||||
control={control}
|
/>
|
||||||
name="provider.policyArns"
|
<Controller
|
||||||
defaultValue=""
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="provider.policyArns"
|
||||||
<FormControl
|
defaultValue=""
|
||||||
label="AWS Policy ARNs"
|
render={({ field, fieldState: { error } }) => (
|
||||||
isError={Boolean(error?.message)}
|
<FormControl
|
||||||
isOptional
|
label="AWS Policy ARNs"
|
||||||
errorText={error?.message}
|
isError={Boolean(error?.message)}
|
||||||
helperText="Generated users will get attached to given policy arns."
|
isOptional
|
||||||
>
|
errorText={error?.message}
|
||||||
<Input
|
helperText="Generated users will get attached to given policy arns."
|
||||||
{...field}
|
>
|
||||||
placeholder="arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
|
<Input
|
||||||
/>
|
{...field}
|
||||||
</FormControl>
|
placeholder="arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
|
||||||
)}
|
/>
|
||||||
/>
|
</FormControl>
|
||||||
<Controller
|
)}
|
||||||
control={control}
|
/>
|
||||||
name="provider.policyDocument"
|
<Controller
|
||||||
render={({ field, fieldState: { error } }) => (
|
control={control}
|
||||||
<FormControl
|
name="provider.policyDocument"
|
||||||
label="AWS IAM Policy Document"
|
render={({ field, fieldState: { error } }) => (
|
||||||
isOptional
|
<FormControl
|
||||||
isError={Boolean(error?.message)}
|
label="AWS IAM Policy Document"
|
||||||
errorText={error?.message}
|
isOptional
|
||||||
helperText="Generated users will have the inline policy."
|
isError={Boolean(error?.message)}
|
||||||
>
|
errorText={error?.message}
|
||||||
<TextArea
|
helperText="Generated users will have the inline policy."
|
||||||
{...field}
|
>
|
||||||
reSize="none"
|
<TextArea
|
||||||
rows={3}
|
{...field}
|
||||||
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
|
reSize="none"
|
||||||
/>
|
rows={3}
|
||||||
</FormControl>
|
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
|
||||||
)}
|
/>
|
||||||
/>
|
</FormControl>
|
||||||
<Controller
|
)}
|
||||||
control={control}
|
/>
|
||||||
name="usernameTemplate"
|
</>
|
||||||
defaultValue=""
|
)}
|
||||||
render={({ field, fieldState: { error } }) => (
|
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
|
||||||
<FormControl
|
<Controller
|
||||||
label="Username Template"
|
control={control}
|
||||||
isError={Boolean(error?.message)}
|
name="usernameTemplate"
|
||||||
errorText={error?.message}
|
defaultValue=""
|
||||||
>
|
render={({ field, fieldState: { error } }) => (
|
||||||
<Input
|
<FormControl
|
||||||
{...field}
|
label="Username Template"
|
||||||
value={field.value || undefined}
|
isError={Boolean(error?.message)}
|
||||||
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
|
errorText={error?.message}
|
||||||
placeholder="{{randomUsername}}"
|
>
|
||||||
/>
|
<Input
|
||||||
</FormControl>
|
{...field}
|
||||||
)}
|
value={field.value || undefined}
|
||||||
/>
|
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
|
||||||
<MetadataForm control={control} name="provider.tags" title="Tags" isValueRequired />
|
placeholder="{{randomUsername}}"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
|
||||||
|
<MetadataForm control={control} name="provider.tags" title="Tags" isValueRequired />
|
||||||
|
)}
|
||||||
{!isSingleEnvironmentMode && (
|
{!isSingleEnvironmentMode && (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
+10
-8
@@ -154,20 +154,22 @@ const renderOutputForm = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (provider === DynamicSecretProviders.AwsIam) {
|
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;
|
ACCESS_KEY: string;
|
||||||
SECRET_ACCESS_KEY: string;
|
SECRET_ACCESS_KEY: string;
|
||||||
USERNAME: string;
|
USERNAME?: string;
|
||||||
|
SESSION_TOKEN?: string;
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<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 Access Key" value={ACCESS_KEY} />
|
||||||
<OutputDisplay
|
<OutputDisplay label="AWS IAM Secret Key" value={SECRET_ACCESS_KEY} />
|
||||||
label="AWS IAM Secret Key"
|
{SESSION_TOKEN && <OutputDisplay label="AWS IAM Session Token" value={SESSION_TOKEN} />}
|
||||||
value={SECRET_ACCESS_KEY}
|
<div className="mt-2 text-xs text-mineshaft-300">
|
||||||
helperText="Important: Copy these credentials now. You will not be able to see them again after you close the modal."
|
Important: Copy these credentials now. You will not be able to see them again after you
|
||||||
/>
|
close the modal.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user