mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-09 11:28:18 +00:00
feat: enhance PAM account handling with type safety and improved response structure
- Introduced type inference for sanitized accounts to ensure consistent data handling. - Updated account response structure to explicitly cast accounts to the sanitized type. - Refined the decryption function to omit sensitive fields from the returned account object. - Improved error handling in SQL resource factory by enforcing required gateway ID validation.
This commit is contained in:
@@ -23,6 +23,8 @@ const SanitizedAccountSchema = z.union([
|
|||||||
SanitizedAwsIamAccountWithResourceSchema
|
SanitizedAwsIamAccountWithResourceSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
type TSanitizedAccount = z.infer<typeof SanitizedAccountSchema>;
|
||||||
|
|
||||||
export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -95,7 +97,7 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { accounts, folders, totalCount, folderId, folderPaths };
|
return { accounts: accounts as TSanitizedAccount[], folders, totalCount, folderId, folderPaths };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -72,17 +72,24 @@ export const decryptAccount = async <
|
|||||||
account: T,
|
account: T,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
): Promise<T & { credentials: TPamAccountCredentials; lastRotationMessage: string | null }> => {
|
): Promise<
|
||||||
|
Omit<T, "encryptedCredentials" | "encryptedLastRotationMessage"> & {
|
||||||
|
credentials: TPamAccountCredentials;
|
||||||
|
lastRotationMessage: string | null;
|
||||||
|
}
|
||||||
|
> => {
|
||||||
|
const { encryptedCredentials, encryptedLastRotationMessage, ...rest } = account;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...account,
|
...rest,
|
||||||
credentials: await decryptAccountCredentials({
|
credentials: await decryptAccountCredentials({
|
||||||
encryptedCredentials: account.encryptedCredentials,
|
encryptedCredentials,
|
||||||
projectId,
|
projectId,
|
||||||
kmsService
|
kmsService
|
||||||
}),
|
}),
|
||||||
lastRotationMessage: account.encryptedLastRotationMessage
|
lastRotationMessage: encryptedLastRotationMessage
|
||||||
? await decryptAccountMessage({
|
? await decryptAccountMessage({
|
||||||
encryptedMessage: account.encryptedLastRotationMessage,
|
encryptedMessage: encryptedLastRotationMessage,
|
||||||
projectId,
|
projectId,
|
||||||
kmsService
|
kmsService
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ export const pamAccountServiceFactory = ({
|
|||||||
const totalCount = totalFolderCount + totalAccountCount;
|
const totalCount = totalFolderCount + totalAccountCount;
|
||||||
|
|
||||||
const decryptedAndPermittedAccounts: Array<
|
const decryptedAndPermittedAccounts: Array<
|
||||||
TPamAccounts & {
|
Omit<TPamAccounts, "encryptedCredentials" | "encryptedLastRotationMessage"> & {
|
||||||
resource: Pick<TPamResources, "id" | "name" | "resourceType"> & { rotationCredentialsConfigured: boolean };
|
resource: Pick<TPamResources, "id" | "name" | "resourceType"> & { rotationCredentialsConfigured: boolean };
|
||||||
credentials: TPamAccountCredentials;
|
credentials: TPamAccountCredentials;
|
||||||
lastRotationMessage: string | null;
|
lastRotationMessage: string | null;
|
||||||
|
|||||||
@@ -170,12 +170,6 @@ export const generateConsoleFederationUrl = async ({
|
|||||||
|
|
||||||
const federationEndpoint = "https://signin.aws.amazon.com/federation";
|
const federationEndpoint = "https://signin.aws.amazon.com/federation";
|
||||||
|
|
||||||
// Console destination can be regional
|
|
||||||
const getConsoleHost = () =>
|
|
||||||
connectionDetails.region === "us-east-1"
|
|
||||||
? "console.aws.amazon.com"
|
|
||||||
: `${connectionDetails.region}.console.aws.amazon.com`;
|
|
||||||
|
|
||||||
const signinTokenUrl = `${federationEndpoint}?Action=getSigninToken&Session=${encodeURIComponent(sessionJson)}`;
|
const signinTokenUrl = `${federationEndpoint}?Action=getSigninToken&Session=${encodeURIComponent(sessionJson)}`;
|
||||||
|
|
||||||
const tokenResponse = await fetch(signinTokenUrl);
|
const tokenResponse = await fetch(signinTokenUrl);
|
||||||
@@ -199,7 +193,7 @@ export const generateConsoleFederationUrl = async ({
|
|||||||
throw new Error(`AWS federation endpoint did not return a SigninToken: ${responseText.substring(0, 200)}`);
|
throw new Error(`AWS federation endpoint did not return a SigninToken: ${responseText.substring(0, 200)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const consoleDestination = `https://${getConsoleHost()}/`;
|
const consoleDestination = `https://console.aws.amazon.com/`;
|
||||||
const consoleUrl = `${federationEndpoint}?Action=login&SigninToken=${encodeURIComponent(tokenData.SigninToken)}&Destination=${encodeURIComponent(consoleDestination)}`;
|
const consoleUrl = `${federationEndpoint}?Action=login&SigninToken=${encodeURIComponent(tokenData.SigninToken)}&Destination=${encodeURIComponent(consoleDestination)}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { AwsIamResourceListItemSchema } from "./aws-iam-resource-schemas";
|
import { AwsIamResourceListItemSchema } from "./aws-iam-resource-schemas";
|
||||||
@@ -14,7 +16,7 @@ export const getAwsIamResourceListItem = () => {
|
|||||||
* ARN format: arn:aws:iam::123456789012:role/RoleName
|
* ARN format: arn:aws:iam::123456789012:role/RoleName
|
||||||
*/
|
*/
|
||||||
export const extractAwsAccountIdFromArn = (roleArn: string): string => {
|
export const extractAwsAccountIdFromArn = (roleArn: string): string => {
|
||||||
const match = roleArn.match(/^arn:aws:iam::(\d{12}):role\//);
|
const match = roleArn.match(new RE2("^arn:aws:iam::(\\d{12}):role/"));
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new BadRequestError({ message: "Invalid IAM Role ARN format" });
|
throw new BadRequestError({ message: "Invalid IAM Role ARN format" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ export const AwsIamAccountSchema = BasePamAccountSchema.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const CreateAwsIamAccountSchema = BaseCreatePamAccountSchema.extend({
|
export const CreateAwsIamAccountSchema = BaseCreatePamAccountSchema.extend({
|
||||||
credentials: AwsIamAccountCredentialsSchema,
|
credentials: AwsIamAccountCredentialsSchema
|
||||||
// AWS IAM doesn't support credential rotation - credentials are generated on-the-fly via STS
|
|
||||||
rotationEnabled: z.boolean().optional().default(false)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UpdateAwsIamAccountSchema = BaseUpdatePamAccountSchema.extend({
|
export const UpdateAwsIamAccountSchema = BaseUpdatePamAccountSchema.extend({
|
||||||
|
|||||||
@@ -233,6 +233,10 @@ export const sqlResourceFactory: TPamResourceFactory<TSqlResourceConnectionDetai
|
|||||||
gatewayV2Service
|
gatewayV2Service
|
||||||
) => {
|
) => {
|
||||||
const validateConnection = async () => {
|
const validateConnection = async () => {
|
||||||
|
if (!gatewayId) {
|
||||||
|
throw new BadRequestError({ message: "Gateway ID is required" });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await executeWithGateway({ connectionDetails, gatewayId, resourceType }, gatewayV2Service, async (client) => {
|
await executeWithGateway({ connectionDetails, gatewayId, resourceType }, gatewayV2Service, async (client) => {
|
||||||
await client.validate(true);
|
await client.validate(true);
|
||||||
@@ -255,6 +259,10 @@ export const sqlResourceFactory: TPamResourceFactory<TSqlResourceConnectionDetai
|
|||||||
credentials
|
credentials
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
|
if (!gatewayId) {
|
||||||
|
throw new BadRequestError({ message: "Gateway ID is required" });
|
||||||
|
}
|
||||||
|
|
||||||
await executeWithGateway(
|
await executeWithGateway(
|
||||||
{
|
{
|
||||||
connectionDetails,
|
connectionDetails,
|
||||||
@@ -296,6 +304,10 @@ export const sqlResourceFactory: TPamResourceFactory<TSqlResourceConnectionDetai
|
|||||||
currentCredentials
|
currentCredentials
|
||||||
) => {
|
) => {
|
||||||
const newPassword = alphaNumericNanoId(32);
|
const newPassword = alphaNumericNanoId(32);
|
||||||
|
if (!gatewayId) {
|
||||||
|
throw new BadRequestError({ message: "Gateway ID is required" });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await executeWithGateway(
|
return await executeWithGateway(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -73,7 +73,11 @@ const CreateForm = ({
|
|||||||
);
|
);
|
||||||
case PamResourceType.AwsIam:
|
case PamResourceType.AwsIam:
|
||||||
return (
|
return (
|
||||||
<AwsIamAccountForm onSubmit={onSubmit} resourceId={resourceId} resourceType={resourceType} />
|
<AwsIamAccountForm
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
resourceId={resourceId}
|
||||||
|
resourceType={resourceType}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled resource: ${resourceType}`);
|
throw new Error(`Unhandled resource: ${resourceType}`);
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export const PamAccountRow = ({
|
|||||||
</span>
|
</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{account.lastRotatedAt && (
|
{"lastRotatedAt" in account && account.lastRotatedAt && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className="max-w-sm text-center"
|
className="max-w-sm text-center"
|
||||||
isDisabled={!account.lastRotationMessage}
|
isDisabled={!account.lastRotationMessage}
|
||||||
|
|||||||
Reference in New Issue
Block a user