feat: enhance AWS IAM resource handling with new gateway access schema and improved project ID management

- Introduced GatewayAccessResponseSchema for consistent response structures across Postgres, MySQL, and SSH resources.
- Updated PAM account router to utilize the new schema, streamlining response validation.
- Refactored AWS IAM service to improve project ID handling during role assumption and credential management.
- Enhanced AWS IAM resource schemas to support gateway-specific configurations, improving flexibility and type safety.
This commit is contained in:
Victor Santos
2025-12-07 20:32:59 -03:00
parent 69fd05bc1e
commit 3e77c33532
11 changed files with 198 additions and 185 deletions
@@ -6,6 +6,7 @@ import { PamAccountOrderBy, PamAccountView } from "@app/ee/services/pam-account/
import { SanitizedAwsIamAccountWithResourceSchema } from "@app/ee/services/pam-resource/aws-iam/aws-iam-resource-schemas"; import { SanitizedAwsIamAccountWithResourceSchema } from "@app/ee/services/pam-resource/aws-iam/aws-iam-resource-schemas";
import { SanitizedMySQLAccountWithResourceSchema } from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas"; import { SanitizedMySQLAccountWithResourceSchema } from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas";
import { PamResource } from "@app/ee/services/pam-resource/pam-resource-enums"; import { PamResource } from "@app/ee/services/pam-resource/pam-resource-enums";
import { GatewayAccessResponseSchema } from "@app/ee/services/pam-resource/pam-resource-schemas";
import { SanitizedPostgresAccountWithResourceSchema } from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas"; import { SanitizedPostgresAccountWithResourceSchema } from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas";
import { SanitizedSSHAccountWithResourceSchema } from "@app/ee/services/pam-resource/ssh/ssh-resource-schemas"; import { SanitizedSSHAccountWithResourceSchema } from "@app/ee/services/pam-resource/ssh/ssh-resource-schemas";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
@@ -130,51 +131,15 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.discriminatedUnion("resourceType", [ 200: z.discriminatedUnion("resourceType", [
// Gateway-based resources (Postgres) // Gateway-based resources (Postgres, MySQL, SSH)
z.object({ GatewayAccessResponseSchema.extend({ resourceType: z.literal(PamResource.Postgres) }),
sessionId: z.string(), GatewayAccessResponseSchema.extend({ resourceType: z.literal(PamResource.MySQL) }),
resourceType: z.literal(PamResource.Postgres), GatewayAccessResponseSchema.extend({ resourceType: z.literal(PamResource.SSH) }),
relayClientCertificate: z.string(),
relayClientPrivateKey: z.string(),
relayServerCertificateChain: z.string(),
gatewayClientCertificate: z.string(),
gatewayClientPrivateKey: z.string(),
gatewayServerCertificateChain: z.string(),
relayHost: z.string(),
metadata: z.record(z.string(), z.string().optional()).optional()
}),
// Gateway-based resources (MySQL)
z.object({
sessionId: z.string(),
resourceType: z.literal(PamResource.MySQL),
relayClientCertificate: z.string(),
relayClientPrivateKey: z.string(),
relayServerCertificateChain: z.string(),
gatewayClientCertificate: z.string(),
gatewayClientPrivateKey: z.string(),
gatewayServerCertificateChain: z.string(),
relayHost: z.string(),
metadata: z.record(z.string(), z.string().optional()).optional()
}),
// Gateway-based resources (SSH)
z.object({
sessionId: z.string(),
resourceType: z.literal(PamResource.SSH),
relayClientCertificate: z.string(),
relayClientPrivateKey: z.string(),
relayServerCertificateChain: z.string(),
gatewayClientCertificate: z.string(),
gatewayClientPrivateKey: z.string(),
gatewayServerCertificateChain: z.string(),
relayHost: z.string(),
metadata: z.record(z.string(), z.string().optional()).optional()
}),
// AWS IAM (no gateway, returns console URL) // AWS IAM (no gateway, returns console URL)
z.object({ z.object({
sessionId: z.string(), sessionId: z.string(),
resourceType: z.literal(PamResource.AwsIam), resourceType: z.literal(PamResource.AwsIam),
consoleUrl: z.string().url(), consoleUrl: z.string().url(),
projectId: z.string().uuid(),
metadata: z.record(z.string(), z.string().optional()).optional() metadata: z.record(z.string(), z.string().optional()).optional()
}) })
]) ])
@@ -203,7 +168,7 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: req.permission.orgId, orgId: req.permission.orgId,
projectId: response.projectId, projectId: req.body.projectId,
event: { event: {
type: EventType.PAM_ACCOUNT_ACCESS, type: EventType.PAM_ACCOUNT_ACCESS,
metadata: { metadata: {
@@ -616,7 +616,6 @@ export const pamAccountServiceFactory = ({
return { return {
sessionId: session.id, sessionId: session.id,
resourceType, resourceType,
projectId: account.projectId,
account, account,
consoleUrl, consoleUrl,
metadata: { metadata: {
@@ -1,8 +1,10 @@
import { AssumeRoleCommand, STSClient, STSClientConfig } from "@aws-sdk/client-sts"; import { AssumeRoleCommand, Credentials, STSClient, STSClientConfig } from "@aws-sdk/client-sts";
import { CustomAWSHasher } from "@app/lib/aws/hashing"; import { CustomAWSHasher } from "@app/lib/aws/hashing";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { TAwsIamResourceConnectionDetails } from "./aws-iam-resource-types"; import { TAwsIamResourceConnectionDetails } from "./aws-iam-resource-types";
@@ -14,42 +16,123 @@ const AWS_STS_MIN_DURATION_SECONDS = 900;
// 3. The target account's resources can be in any region - it doesn't affect STS calls // 3. The target account's resources can be in any region - it doesn't affect STS calls
const AWS_STS_DEFAULT_REGION = "us-east-1"; const AWS_STS_DEFAULT_REGION = "us-east-1";
const createStsClient = (): STSClient => { const createStsClient = (credentials?: Credentials): STSClient => {
const appCfg = getConfig(); const appCfg = getConfig();
const config: STSClientConfig = { const config: STSClientConfig = {
region: AWS_STS_DEFAULT_REGION, region: AWS_STS_DEFAULT_REGION,
useFipsEndpoint: crypto.isFipsModeEnabled(), useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher, 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 // if hosting on AWS
}; };
if (credentials) {
// Use provided credentials (for role chaining)
config.credentials = {
accessKeyId: credentials.AccessKeyId!,
secretAccessKey: credentials.SecretAccessKey!,
sessionToken: credentials.SessionToken
};
} else if (appCfg.PAM_AWS_ACCESS_KEY_ID && appCfg.PAM_AWS_SECRET_ACCESS_KEY) {
// Use configured static credentials
config.credentials = {
accessKeyId: appCfg.PAM_AWS_ACCESS_KEY_ID,
secretAccessKey: appCfg.PAM_AWS_SECRET_ACCESS_KEY
};
}
// Otherwise uses instance profile if hosting on AWS
return new STSClient(config); return new STSClient(config);
}; };
/**
* Assumes the PAM role and returns the credentials.
* Returns null if assumption fails (for validation) or throws if throwOnError is true.
*/
const assumePamRole = async ({
connectionDetails,
projectId,
sessionDuration = AWS_STS_MIN_DURATION_SECONDS,
sessionNameSuffix = "validation",
throwOnError = false
}: {
connectionDetails: TAwsIamResourceConnectionDetails;
projectId: string;
sessionDuration?: number;
sessionNameSuffix?: string;
throwOnError?: boolean;
}): Promise<Credentials | null> => {
const stsClient = createStsClient();
const result = await stsClient.send(
new AssumeRoleCommand({
RoleArn: connectionDetails.roleArn,
RoleSessionName: `infisical-pam-${sessionNameSuffix}-${Date.now()}`,
DurationSeconds: sessionDuration,
ExternalId: projectId
})
);
if (!result.Credentials) {
if (throwOnError) {
throw new InternalServerError({
message: "Failed to assume PAM role - AWS STS did not return credentials"
});
}
return null;
}
return result.Credentials;
};
/**
* Assumes a target role using PAM role credentials (role chaining).
* Returns null if assumption fails (for validation) or throws if throwOnError is true.
*/
const assumeTargetRole = async ({
pamCredentials,
targetRoleArn,
projectId,
roleSessionName,
sessionDuration = AWS_STS_MIN_DURATION_SECONDS,
throwOnError = false
}: {
pamCredentials: Credentials;
targetRoleArn: string;
projectId: string;
roleSessionName: string;
sessionDuration?: number;
throwOnError?: boolean;
}): Promise<Credentials | null> => {
const chainedStsClient = createStsClient(pamCredentials);
const result = await chainedStsClient.send(
new AssumeRoleCommand({
RoleArn: targetRoleArn,
RoleSessionName: roleSessionName,
DurationSeconds: sessionDuration,
ExternalId: projectId
})
);
if (!result.Credentials) {
if (throwOnError) {
throw new BadRequestError({
message: "Failed to assume target role - verify the target role trust policy allows the PAM role to assume it"
});
}
return null;
}
return result.Credentials;
};
export const validatePamRoleConnection = async ( export const validatePamRoleConnection = async (
connectionDetails: TAwsIamResourceConnectionDetails, connectionDetails: TAwsIamResourceConnectionDetails,
projectId: string projectId: string
): Promise<boolean> => { ): Promise<boolean> => {
const stsClient = createStsClient();
try { try {
await stsClient.send( const credentials = await assumePamRole({ connectionDetails, projectId });
new AssumeRoleCommand({ return credentials !== null;
RoleArn: connectionDetails.roleArn,
RoleSessionName: `infisical-pam-validation-${Date.now()}`,
DurationSeconds: AWS_STS_MIN_DURATION_SECONDS,
ExternalId: projectId
})
);
return true;
} catch { } catch {
return false; return false;
} }
@@ -64,45 +147,17 @@ export const validateTargetRoleAssumption = async ({
targetRoleArn: string; targetRoleArn: string;
projectId: string; projectId: string;
}): Promise<boolean> => { }): Promise<boolean> => {
const stsClient = createStsClient();
try { try {
// First assume the PAM role const pamCredentials = await assumePamRole({ connectionDetails, projectId });
const pamRoleCredentials = await stsClient.send( if (!pamCredentials) return false;
new AssumeRoleCommand({
RoleArn: connectionDetails.roleArn,
RoleSessionName: `infisical-pam-validation-${Date.now()}`,
DurationSeconds: AWS_STS_MIN_DURATION_SECONDS,
ExternalId: projectId
})
);
if (!pamRoleCredentials.Credentials) { const targetCredentials = await assumeTargetRole({
return false; pamCredentials,
} targetRoleArn,
projectId,
// Then use the PAM role credentials to assume the target role roleSessionName: `infisical-pam-target-validation-${Date.now()}`
const pamStsClient = new STSClient({
region: AWS_STS_DEFAULT_REGION,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher,
credentials: {
accessKeyId: pamRoleCredentials.Credentials.AccessKeyId!,
secretAccessKey: pamRoleCredentials.Credentials.SecretAccessKey!,
sessionToken: pamRoleCredentials.Credentials.SessionToken
}
}); });
return targetCredentials !== null;
await pamStsClient.send(
new AssumeRoleCommand({
RoleArn: targetRoleArn,
RoleSessionName: `infisical-pam-target-validation-${Date.now()}`,
DurationSeconds: AWS_STS_MIN_DURATION_SECONDS,
ExternalId: projectId
})
);
return true;
} catch { } catch {
return false; return false;
} }
@@ -124,48 +179,24 @@ export const generateConsoleFederationUrl = async ({
projectId: string; projectId: string;
sessionDuration: number; sessionDuration: number;
}): Promise<{ consoleUrl: string; expiresAt: Date }> => { }): Promise<{ consoleUrl: string; expiresAt: Date }> => {
const stsClient = createStsClient(); const pamCredentials = await assumePamRole({
connectionDetails,
// First assume the PAM role projectId,
const pamRoleCredentials = await stsClient.send( sessionDuration,
new AssumeRoleCommand({ sessionNameSuffix: "session",
RoleArn: connectionDetails.roleArn, throwOnError: true
RoleSessionName: `infisical-pam-${Date.now()}`,
DurationSeconds: sessionDuration,
ExternalId: projectId
})
);
if (!pamRoleCredentials.Credentials) {
throw new Error("Failed to assume PAM role");
}
// Role chaining: use PAM role credentials to assume the target role
const pamStsClient = new STSClient({
region: AWS_STS_DEFAULT_REGION,
useFipsEndpoint: crypto.isFipsModeEnabled(),
sha256: CustomAWSHasher,
credentials: {
accessKeyId: pamRoleCredentials.Credentials.AccessKeyId!,
secretAccessKey: pamRoleCredentials.Credentials.SecretAccessKey!,
sessionToken: pamRoleCredentials.Credentials.SessionToken
}
}); });
const targetRoleCredentials = await pamStsClient.send( const targetCredentials = await assumeTargetRole({
new AssumeRoleCommand({ pamCredentials: pamCredentials!,
RoleArn: targetRoleArn, targetRoleArn,
RoleSessionName: roleSessionName, projectId,
DurationSeconds: sessionDuration, roleSessionName,
ExternalId: projectId sessionDuration,
}) throwOnError: true
); });
if (!targetRoleCredentials.Credentials) { const { AccessKeyId, SecretAccessKey, SessionToken, Expiration } = targetCredentials!;
throw new Error("Failed to assume target role");
}
const { AccessKeyId, SecretAccessKey, SessionToken, Expiration } = targetRoleCredentials.Credentials;
// Generate federation URL // Generate federation URL
const sessionJson = JSON.stringify({ const sessionJson = JSON.stringify({
@@ -178,25 +209,20 @@ export const generateConsoleFederationUrl = async ({
const signinTokenUrl = `${federationEndpoint}?Action=getSigninToken&Session=${encodeURIComponent(sessionJson)}`; const signinTokenUrl = `${federationEndpoint}?Action=getSigninToken&Session=${encodeURIComponent(sessionJson)}`;
const tokenResponse = await fetch(signinTokenUrl); const tokenResponse = await request.get<{ SigninToken?: string }>(signinTokenUrl);
if (!tokenResponse.ok) { if (!tokenResponse.data.SigninToken) {
const errorText = await tokenResponse.text(); throw new InternalServerError({
// eslint-disable-next-line no-console message: `AWS federation endpoint did not return a SigninToken: ${JSON.stringify(tokenResponse.data).substring(0, 200)}`
throw new Error(`AWS federation endpoint returned error (${tokenResponse.status}): ${errorText.substring(0, 200)}`); });
} }
const responseText = await tokenResponse.text(); const tokenData = tokenResponse.data;
let tokenData: { SigninToken: string };
try {
tokenData = JSON.parse(responseText) as { SigninToken: string };
} catch {
throw new Error(`AWS federation endpoint returned invalid response: ${responseText.substring(0, 200)}`);
}
if (!tokenData.SigninToken) { if (!tokenData.SigninToken) {
throw new Error(`AWS federation endpoint did not return a SigninToken: ${responseText.substring(0, 200)}`); throw new InternalServerError({
message: `AWS federation endpoint did not return a SigninToken: ${JSON.stringify(tokenResponse.data).substring(0, 200)}`
});
} }
const consoleDestination = `https://console.aws.amazon.com/`; const consoleDestination = `https://console.aws.amazon.com/`;
@@ -51,13 +51,11 @@ export const AwsIamResourceListItemSchema = z.object({
export const CreateAwsIamResourceSchema = BaseCreatePamResourceSchema.extend({ export const CreateAwsIamResourceSchema = BaseCreatePamResourceSchema.extend({
connectionDetails: AwsIamResourceConnectionDetailsSchema, connectionDetails: AwsIamResourceConnectionDetailsSchema,
gatewayId: z.string().uuid().nullable().optional(),
rotationAccountCredentials: AwsIamAccountCredentialsSchema.nullable().optional() rotationAccountCredentials: AwsIamAccountCredentialsSchema.nullable().optional()
}); });
export const UpdateAwsIamResourceSchema = BaseUpdatePamResourceSchema.extend({ export const UpdateAwsIamResourceSchema = BaseUpdatePamResourceSchema.extend({
connectionDetails: AwsIamResourceConnectionDetailsSchema.optional(), connectionDetails: AwsIamResourceConnectionDetailsSchema.optional(),
gatewayId: z.string().uuid().nullable().optional(),
rotationAccountCredentials: AwsIamAccountCredentialsSchema.nullable().optional() rotationAccountCredentials: AwsIamAccountCredentialsSchema.nullable().optional()
}); });
@@ -2,13 +2,13 @@ import { z } from "zod";
import { PamResource } from "../pam-resource-enums"; import { PamResource } from "../pam-resource-enums";
import { import {
BaseCreateGatewayPamResourceSchema,
BaseCreatePamAccountSchema, BaseCreatePamAccountSchema,
BaseCreatePamResourceSchema,
BasePamAccountSchema, BasePamAccountSchema,
BasePamAccountSchemaWithResource, BasePamAccountSchemaWithResource,
BasePamResourceSchema, BasePamResourceSchema,
BaseUpdatePamAccountSchema, BaseUpdateGatewayPamResourceSchema,
BaseUpdatePamResourceSchema BaseUpdatePamAccountSchema
} from "../pam-resource-schemas"; } from "../pam-resource-schemas";
import { import {
BaseSqlAccountCredentialsSchema, BaseSqlAccountCredentialsSchema,
@@ -43,12 +43,12 @@ export const MySQLResourceListItemSchema = z.object({
resource: z.literal(PamResource.MySQL) resource: z.literal(PamResource.MySQL)
}); });
export const CreateMySQLResourceSchema = BaseCreatePamResourceSchema.extend({ export const CreateMySQLResourceSchema = BaseCreateGatewayPamResourceSchema.extend({
connectionDetails: MySQLResourceConnectionDetailsSchema, connectionDetails: MySQLResourceConnectionDetailsSchema,
rotationAccountCredentials: MySQLAccountCredentialsSchema.nullable().optional() rotationAccountCredentials: MySQLAccountCredentialsSchema.nullable().optional()
}); });
export const UpdateMySQLResourceSchema = BaseUpdatePamResourceSchema.extend({ export const UpdateMySQLResourceSchema = BaseUpdateGatewayPamResourceSchema.extend({
connectionDetails: MySQLResourceConnectionDetailsSchema.optional(), connectionDetails: MySQLResourceConnectionDetailsSchema.optional(),
rotationAccountCredentials: MySQLAccountCredentialsSchema.nullable().optional() rotationAccountCredentials: MySQLAccountCredentialsSchema.nullable().optional()
}); });
@@ -3,6 +3,18 @@ import { z } from "zod";
import { PamAccountsSchema, PamResourcesSchema } from "@app/db/schemas"; import { PamAccountsSchema, PamResourcesSchema } from "@app/db/schemas";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
export const GatewayAccessResponseSchema = z.object({
sessionId: z.string(),
relayClientCertificate: z.string(),
relayClientPrivateKey: z.string(),
relayServerCertificateChain: z.string(),
gatewayClientCertificate: z.string(),
gatewayClientPrivateKey: z.string(),
gatewayServerCertificateChain: z.string(),
relayHost: z.string(),
metadata: z.record(z.string(), z.string().optional()).optional()
});
// Resources // Resources
export const BasePamResourceSchema = PamResourcesSchema.omit({ export const BasePamResourceSchema = PamResourcesSchema.omit({
encryptedConnectionDetails: true, encryptedConnectionDetails: true,
@@ -10,17 +22,27 @@ export const BasePamResourceSchema = PamResourcesSchema.omit({
resourceType: true resourceType: true
}); });
export const BaseCreatePamResourceSchema = z.object({ const CoreCreatePamResourceSchema = z.object({
projectId: z.string().uuid(), projectId: z.string().uuid(),
gatewayId: z.string().uuid(),
name: slugSchema({ field: "name" }) name: slugSchema({ field: "name" })
}); });
export const BaseUpdatePamResourceSchema = z.object({ export const BaseCreateGatewayPamResourceSchema = CoreCreatePamResourceSchema.extend({
gatewayId: z.string().uuid().optional(), gatewayId: z.string().uuid()
});
export const BaseCreatePamResourceSchema = CoreCreatePamResourceSchema;
const CoreUpdatePamResourceSchema = z.object({
name: slugSchema({ field: "name" }).optional() name: slugSchema({ field: "name" }).optional()
}); });
export const BaseUpdateGatewayPamResourceSchema = CoreUpdatePamResourceSchema.extend({
gatewayId: z.string().uuid().optional()
});
export const BaseUpdatePamResourceSchema = CoreUpdatePamResourceSchema;
// Accounts // Accounts
export const BasePamAccountSchema = PamAccountsSchema.omit({ export const BasePamAccountSchema = PamAccountsSchema.omit({
encryptedCredentials: true encryptedCredentials: true
@@ -2,13 +2,13 @@ import { z } from "zod";
import { PamResource } from "../pam-resource-enums"; import { PamResource } from "../pam-resource-enums";
import { import {
BaseCreateGatewayPamResourceSchema,
BaseCreatePamAccountSchema, BaseCreatePamAccountSchema,
BaseCreatePamResourceSchema,
BasePamAccountSchema, BasePamAccountSchema,
BasePamAccountSchemaWithResource, BasePamAccountSchemaWithResource,
BasePamResourceSchema, BasePamResourceSchema,
BaseUpdatePamAccountSchema, BaseUpdateGatewayPamResourceSchema,
BaseUpdatePamResourceSchema BaseUpdatePamAccountSchema
} from "../pam-resource-schemas"; } from "../pam-resource-schemas";
import { import {
BaseSqlAccountCredentialsSchema, BaseSqlAccountCredentialsSchema,
@@ -40,12 +40,12 @@ export const PostgresResourceListItemSchema = z.object({
resource: z.literal(PamResource.Postgres) resource: z.literal(PamResource.Postgres)
}); });
export const CreatePostgresResourceSchema = BaseCreatePamResourceSchema.extend({ export const CreatePostgresResourceSchema = BaseCreateGatewayPamResourceSchema.extend({
connectionDetails: PostgresResourceConnectionDetailsSchema, connectionDetails: PostgresResourceConnectionDetailsSchema,
rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional() rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional()
}); });
export const UpdatePostgresResourceSchema = BaseUpdatePamResourceSchema.extend({ export const UpdatePostgresResourceSchema = BaseUpdateGatewayPamResourceSchema.extend({
connectionDetails: PostgresResourceConnectionDetailsSchema.optional(), connectionDetails: PostgresResourceConnectionDetailsSchema.optional(),
rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional() rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional()
}); });
@@ -2,13 +2,13 @@ import { z } from "zod";
import { PamResource } from "../pam-resource-enums"; import { PamResource } from "../pam-resource-enums";
import { import {
BaseCreateGatewayPamResourceSchema,
BaseCreatePamAccountSchema, BaseCreatePamAccountSchema,
BaseCreatePamResourceSchema,
BasePamAccountSchema, BasePamAccountSchema,
BasePamAccountSchemaWithResource, BasePamAccountSchemaWithResource,
BasePamResourceSchema, BasePamResourceSchema,
BaseUpdatePamAccountSchema, BaseUpdateGatewayPamResourceSchema,
BaseUpdatePamResourceSchema BaseUpdatePamAccountSchema
} from "../pam-resource-schemas"; } from "../pam-resource-schemas";
import { SSHAuthMethod } from "./ssh-resource-enums"; import { SSHAuthMethod } from "./ssh-resource-enums";
@@ -73,12 +73,12 @@ export const SanitizedSSHResourceSchema = BaseSSHResourceSchema.extend({
.optional() .optional()
}); });
export const CreateSSHResourceSchema = BaseCreatePamResourceSchema.extend({ export const CreateSSHResourceSchema = BaseCreateGatewayPamResourceSchema.extend({
connectionDetails: SSHResourceConnectionDetailsSchema, connectionDetails: SSHResourceConnectionDetailsSchema,
rotationAccountCredentials: SSHAccountCredentialsSchema.nullable().optional() rotationAccountCredentials: SSHAccountCredentialsSchema.nullable().optional()
}); });
export const UpdateSSHResourceSchema = BaseUpdatePamResourceSchema.extend({ export const UpdateSSHResourceSchema = BaseUpdateGatewayPamResourceSchema.extend({
connectionDetails: SSHResourceConnectionDetailsSchema.optional(), connectionDetails: SSHResourceConnectionDetailsSchema.optional(),
rotationAccountCredentials: SSHAccountCredentialsSchema.nullable().optional() rotationAccountCredentials: SSHAccountCredentialsSchema.nullable().optional()
}); });
+4
View File
@@ -286,6 +286,10 @@ const envSchema = z
DYNAMIC_SECRET_AWS_SECRET_ACCESS_KEY: zpStr(z.string().optional()).default( DYNAMIC_SECRET_AWS_SECRET_ACCESS_KEY: zpStr(z.string().optional()).default(
process.env.INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY process.env.INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY
), ),
// PAM AWS credentials (for AWS IAM PAM resource type)
PAM_AWS_ACCESS_KEY_ID: zpStr(z.string().optional()),
PAM_AWS_SECRET_ACCESS_KEY: zpStr(z.string().optional()),
/* ----------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------- */
/* App Connections ----------------------------------------------------------------------------- */ /* App Connections ----------------------------------------------------------------------------- */
@@ -18,7 +18,7 @@ import { CopyButton } from "@app/components/v2/CopyButton";
import { useProject } from "@app/context"; import { useProject } from "@app/context";
import { PamResourceType, TAwsIamAccount } from "@app/hooks/api/pam"; import { PamResourceType, TAwsIamAccount } from "@app/hooks/api/pam";
import { GenericAccountFields } from "./GenericAccountFields"; import { GenericAccountFields, genericAccountFieldsSchema } from "./GenericAccountFields";
type Props = { type Props = {
account?: TAwsIamAccount; account?: TAwsIamAccount;
@@ -45,12 +45,7 @@ const AwsIamCredentialsSchema = z.object({
.default(3600) .default(3600)
}); });
const genericAwsIamAccountFieldsSchema = z.object({ const formSchema = genericAccountFieldsSchema.extend({
name: z.string().min(1, "Name is required").max(64, "Name must be at most 64 characters"),
description: z.string().max(512).optional().nullable()
});
const formSchema = genericAwsIamAccountFieldsSchema.extend({
credentials: AwsIamCredentialsSchema credentials: AwsIamCredentialsSchema
}); });
@@ -153,8 +148,8 @@ export const AwsIamAccountForm = ({ account, onSubmit }: Props) => {
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className="px-4 pb-2.5"> <AccordionContent className="px-4 pb-2.5">
<p className="mb-3 text-sm text-mineshaft-300"> <p className="mb-3 text-sm text-mineshaft-300">
The target role must have a trust policy that allows the Infisical PAM role to The target role must have a trust policy that allows the Infisical PAM role you
assume it. If you used the{" "} created and used in the &quot;Resources&quot; tab to assume it. If you used the{" "}
<code className="rounded bg-mineshaft-700 px-1 text-xs">infisical-pam-*</code>{" "} <code className="rounded bg-mineshaft-700 px-1 text-xs">infisical-pam-*</code>{" "}
naming convention, no additional changes are needed to the PAM role. naming convention, no additional changes are needed to the PAM role.
</p> </p>
@@ -66,7 +66,7 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
"Statement": [{ "Statement": [{
"Effect": "Allow", "Effect": "Allow",
"Principal": { "Principal": {
"AWS": "arn:aws:iam::${INFISICAL_AWS_ACCOUNT_US}:root" "AWS": "arn:aws:iam::<INFISICAL_AWS_ACCOUNT_ID>:root"
}, },
"Action": "sts:AssumeRole", "Action": "sts:AssumeRole",
"Condition": { "Condition": {
@@ -186,7 +186,11 @@ export const AwsIamResourceForm = ({ resource, onSubmit }: Props) => {
<code className="rounded bg-mineshaft-700 px-1 font-bold"> <code className="rounded bg-mineshaft-700 px-1 font-bold">
{INFISICAL_AWS_ACCOUNT_EU} {INFISICAL_AWS_ACCOUNT_EU}
</code>{" "} </code>{" "}
for EU region. The External ID{" "} for EU region. Replace{" "}
<code className="rounded bg-mineshaft-700 px-1 font-bold">
&lt;INFISICAL_AWS_ACCOUNT_ID&gt;
</code>{" "}
with the appropriate Infisical AWS account ID for your region. The External ID{" "}
<code className="rounded bg-mineshaft-700 px-1 font-bold">{projectId}</code> is your <code className="rounded bg-mineshaft-700 px-1 font-bold">{projectId}</code> is your
current project ID. current project ID.
</p> </p>