mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 17:29:00 +00:00
pam: rotation status & error improvements
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasColumn(TableName.PamAccount, "rotationStatus"))) {
|
||||
await knex.schema.alterTable(TableName.PamAccount, (t) => {
|
||||
t.string("rotationStatus").nullable();
|
||||
});
|
||||
}
|
||||
if (!(await knex.schema.hasColumn(TableName.PamAccount, "encryptedLastRotationMessage"))) {
|
||||
await knex.schema.alterTable(TableName.PamAccount, (t) => {
|
||||
t.binary("encryptedLastRotationMessage").nullable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasColumn(TableName.PamAccount, "rotationStatus")) {
|
||||
await knex.schema.alterTable(TableName.PamAccount, (t) => {
|
||||
t.dropColumn("rotationStatus");
|
||||
});
|
||||
}
|
||||
if (await knex.schema.hasColumn(TableName.PamAccount, "encryptedLastRotationMessage")) {
|
||||
await knex.schema.alterTable(TableName.PamAccount, (t) => {
|
||||
t.dropColumn("encryptedLastRotationMessage");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ export const PamAccountsSchema = z.object({
|
||||
updatedAt: z.date(),
|
||||
rotationEnabled: z.boolean().default(false),
|
||||
rotationIntervalSeconds: z.number().nullable().optional(),
|
||||
lastRotatedAt: z.date().nullable().optional()
|
||||
lastRotatedAt: z.date().nullable().optional(),
|
||||
rotationStatus: z.string().nullable().optional(),
|
||||
encryptedLastRotationMessage: zodBuffer.nullable().optional()
|
||||
});
|
||||
|
||||
export type TPamAccounts = z.infer<typeof PamAccountsSchema>;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
CreateMySQLResourceSchema,
|
||||
MySQLResourceSchema,
|
||||
UpdateMySQLResourceSchema
|
||||
} from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas";
|
||||
import { PamResource } from "@app/ee/services/pam-resource/pam-resource-enums";
|
||||
import {
|
||||
CreatePostgresResourceSchema,
|
||||
SanitizedPostgresResourceSchema,
|
||||
UpdatePostgresResourceSchema
|
||||
} from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas";
|
||||
import {
|
||||
CreateMySQLResourceSchema,
|
||||
MySQLResourceSchema,
|
||||
UpdateMySQLResourceSchema
|
||||
} from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas";
|
||||
|
||||
import { registerPamResourceEndpoints } from "./pam-resource-endpoints";
|
||||
|
||||
|
||||
@@ -45,17 +45,47 @@ export const decryptAccountCredentials = async ({
|
||||
return JSON.parse(decryptedPlainTextBlob.toString()) as TPamAccountCredentials;
|
||||
};
|
||||
|
||||
export const decryptAccount = async <T extends { encryptedCredentials: Buffer }>(
|
||||
export const decryptAccountMessage = async ({
|
||||
projectId,
|
||||
encryptedMessage,
|
||||
kmsService
|
||||
}: {
|
||||
projectId: string;
|
||||
encryptedMessage: Buffer;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
}) => {
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
const decryptedPlainTextBlob = decryptor({
|
||||
cipherTextBlob: encryptedMessage
|
||||
});
|
||||
|
||||
return decryptedPlainTextBlob.toString();
|
||||
};
|
||||
|
||||
export const decryptAccount = async <
|
||||
T extends { encryptedCredentials: Buffer; encryptedLastRotationMessage?: Buffer | null }
|
||||
>(
|
||||
account: T,
|
||||
projectId: string,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
): Promise<T & { credentials: TPamAccountCredentials }> => {
|
||||
): Promise<T & { credentials: TPamAccountCredentials; lastRotationMessage: string | null }> => {
|
||||
return {
|
||||
...account,
|
||||
credentials: await decryptAccountCredentials({
|
||||
encryptedCredentials: account.encryptedCredentials,
|
||||
projectId,
|
||||
kmsService
|
||||
})
|
||||
} as T & { credentials: TPamAccountCredentials };
|
||||
}),
|
||||
lastRotationMessage: account.encryptedLastRotationMessage
|
||||
? await decryptAccountMessage({
|
||||
encryptedMessage: account.encryptedLastRotationMessage,
|
||||
projectId,
|
||||
kmsService
|
||||
})
|
||||
: null
|
||||
};
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import { logger } from "@app/lib/logger";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
|
||||
@@ -353,6 +354,7 @@ export const pamAccountServiceFactory = ({
|
||||
TPamAccounts & {
|
||||
resource: Pick<TPamResources, "id" | "name" | "resourceType"> & { rotationCredentialsConfigured: boolean };
|
||||
credentials: TPamAccountCredentials;
|
||||
lastRotationMessage: string | null;
|
||||
}
|
||||
> = [];
|
||||
|
||||
@@ -376,6 +378,7 @@ export const pamAccountServiceFactory = ({
|
||||
) {
|
||||
// Decrypt the account only if the user has permission to read it
|
||||
const decryptedAccount = await decryptAccount(account, account.projectId, kmsService);
|
||||
|
||||
decryptedAndPermittedAccounts.push({
|
||||
...decryptedAccount,
|
||||
resource: {
|
||||
@@ -619,7 +622,9 @@ export const pamAccountServiceFactory = ({
|
||||
account.id,
|
||||
{
|
||||
encryptedCredentials,
|
||||
lastRotatedAt: new Date()
|
||||
lastRotatedAt: new Date(),
|
||||
rotationStatus: "success",
|
||||
encryptedLastRotationMessage: null
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -645,6 +650,24 @@ export const pamAccountServiceFactory = ({
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
|
||||
|
||||
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId: account.projectId
|
||||
});
|
||||
|
||||
const { cipherTextBlob: encryptedMessage } = encryptor({
|
||||
plainText: Buffer.from(errorMessage)
|
||||
});
|
||||
|
||||
await pamAccountDAL.updateById(
|
||||
account.id,
|
||||
{
|
||||
rotationStatus: "failed",
|
||||
encryptedLastRotationMessage: encryptedMessage
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await auditLogService.createAuditLog({
|
||||
projectId: account.projectId,
|
||||
actor: {
|
||||
@@ -662,7 +685,6 @@ export const pamAccountServiceFactory = ({
|
||||
}
|
||||
}
|
||||
});
|
||||
throw error; // Rollback transaction
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -33,7 +33,9 @@ export const BasePamAccountSchemaWithResource = BasePamAccountSchema.extend({
|
||||
resourceType: true
|
||||
}).extend({
|
||||
rotationCredentialsConfigured: z.boolean()
|
||||
})
|
||||
}),
|
||||
lastRotationMessage: z.string().nullable().optional(),
|
||||
rotationStatus: z.string().nullable().optional()
|
||||
});
|
||||
|
||||
export const BaseCreatePamAccountSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user