Merge branch 'heads/main' into daniel/redis-secret-rotation-new

This commit is contained in:
Daniel Hougaard
2025-09-24 00:17:57 +04:00
196 changed files with 10535 additions and 919 deletions
@@ -320,10 +320,20 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
.array(),
secretPath: z.string(),
commits: secretRawSchema
.omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true })
.omit({
_id: true,
environment: true,
workspace: true,
type: true,
version: true,
secretValue: true,
secretComment: true
})
.extend({
secretValueHidden: z.boolean(),
secretValue: z.string().optional(),
secretComment: z.string().optional(),
skipMultilineEncoding: z.boolean().nullish(),
isRotatedSecret: z.boolean().optional(),
op: z.string(),
tags: SanitizedTagSchema.array().optional(),
@@ -348,7 +358,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
secretValueHidden: z.boolean(),
secretComment: z.string().optional(),
tags: SanitizedTagSchema.array().optional(),
secretMetadata: ResourceMetadataSchema.nullish()
secretMetadata: ResourceMetadataSchema.nullish(),
skipMultilineEncoding: z.boolean().nullish()
})
.optional()
})
@@ -407,6 +407,14 @@ export enum EventType {
SECRET_SYNC_SYNC_SECRETS = "secret-sync-sync-secrets",
SECRET_SYNC_IMPORT_SECRETS = "secret-sync-import-secrets",
SECRET_SYNC_REMOVE_SECRETS = "secret-sync-remove-secrets",
GET_PKI_SYNCS = "get-pki-syncs",
GET_PKI_SYNC = "get-pki-sync",
CREATE_PKI_SYNC = "create-pki-sync",
UPDATE_PKI_SYNC = "update-pki-sync",
DELETE_PKI_SYNC = "delete-pki-sync",
PKI_SYNC_SYNC_CERTIFICATES = "pki-sync-sync-certificates",
PKI_SYNC_IMPORT_CERTIFICATES = "pki-sync-import-certificates",
PKI_SYNC_REMOVE_CERTIFICATES = "pki-sync-remove-certificates",
OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER = "oidc-group-membership-mapping-assign-user",
OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER = "oidc-group-membership-mapping-remove-user",
CREATE_KMIP_CLIENT = "create-kmip-client",
@@ -2959,6 +2967,77 @@ interface SecretSyncRemoveSecretsEvent {
};
}
interface GetPkiSyncsEvent {
type: EventType.GET_PKI_SYNCS;
metadata: {
projectId: string;
};
}
interface GetPkiSyncEvent {
type: EventType.GET_PKI_SYNC;
metadata: {
destination: string;
syncId: string;
};
}
interface CreatePkiSyncEvent {
type: EventType.CREATE_PKI_SYNC;
metadata: {
pkiSyncId: string;
name: string;
destination: string;
};
}
interface UpdatePkiSyncEvent {
type: EventType.UPDATE_PKI_SYNC;
metadata: {
pkiSyncId: string;
name: string;
};
}
interface DeletePkiSyncEvent {
type: EventType.DELETE_PKI_SYNC;
metadata: {
pkiSyncId: string;
name: string;
destination: string;
};
}
interface PkiSyncSyncCertificatesEvent {
type: EventType.PKI_SYNC_SYNC_CERTIFICATES;
metadata: {
syncId: string;
syncMessage: string | null;
jobId: string;
jobRanAt: Date;
};
}
interface PkiSyncImportCertificatesEvent {
type: EventType.PKI_SYNC_IMPORT_CERTIFICATES;
metadata: {
syncId: string;
importMessage: string | null;
jobId: string;
jobRanAt: Date;
};
}
interface PkiSyncRemoveCertificatesEvent {
type: EventType.PKI_SYNC_REMOVE_CERTIFICATES;
metadata: {
syncId: string;
removeMessage: string | null;
jobId: string;
jobRanAt: Date;
};
}
interface OidcGroupMembershipMappingAssignUserEvent {
type: EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER;
metadata: {
@@ -3859,6 +3938,14 @@ export type Event =
| SecretSyncSyncSecretsEvent
| SecretSyncImportSecretsEvent
| SecretSyncRemoveSecretsEvent
| GetPkiSyncsEvent
| GetPkiSyncEvent
| CreatePkiSyncEvent
| UpdatePkiSyncEvent
| DeletePkiSyncEvent
| PkiSyncSyncCertificatesEvent
| PkiSyncImportCertificatesEvent
| PkiSyncRemoveCertificatesEvent
| OidcGroupMembershipMappingAssignUserEvent
| OidcGroupMembershipMappingRemoveUserEvent
| CreateKmipClientEvent
@@ -31,6 +31,7 @@ export const getDefaultOnPremFeatures = () => {
caCrl: false,
sshHostGroups: false,
enterpriseSecretSyncs: false,
enterpriseCertificateSyncs: false,
enterpriseAppConnections: true,
machineIdentityAuthTemplates: false
};
@@ -62,6 +62,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
sshHostGroups: false,
secretScanning: false,
enterpriseSecretSyncs: false,
enterpriseCertificateSyncs: false,
enterpriseAppConnections: false,
fips: false,
eventSubscriptions: false,
@@ -75,6 +75,7 @@ export type TFeatureSet = {
sshHostGroups: false;
secretScanning: false;
enterpriseSecretSyncs: false;
enterpriseCertificateSyncs: false;
enterpriseAppConnections: false;
machineIdentityAuthTemplates: false;
fips: false;
@@ -13,6 +13,7 @@ import {
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionPkiSyncActions,
ProjectPermissionPkiTemplateActions,
ProjectPermissionSecretActions,
ProjectPermissionSecretEventActions,
@@ -209,6 +210,19 @@ const buildAdminPermissionRules = () => {
ProjectPermissionSub.SecretSyncs
);
can(
[
ProjectPermissionPkiSyncActions.Create,
ProjectPermissionPkiSyncActions.Edit,
ProjectPermissionPkiSyncActions.Delete,
ProjectPermissionPkiSyncActions.Read,
ProjectPermissionPkiSyncActions.SyncCertificates,
ProjectPermissionPkiSyncActions.ImportCertificates,
ProjectPermissionPkiSyncActions.RemoveCertificates
],
ProjectPermissionSub.PkiSyncs
);
can(
[
ProjectPermissionKmipActions.CreateClients,
@@ -462,6 +476,19 @@ const buildMemberPermissionRules = () => {
ProjectPermissionSub.SecretSyncs
);
can(
[
ProjectPermissionPkiSyncActions.Create,
ProjectPermissionPkiSyncActions.Edit,
ProjectPermissionPkiSyncActions.Delete,
ProjectPermissionPkiSyncActions.Read,
ProjectPermissionPkiSyncActions.SyncCertificates,
ProjectPermissionPkiSyncActions.ImportCertificates,
ProjectPermissionPkiSyncActions.RemoveCertificates
],
ProjectPermissionSub.PkiSyncs
);
can(
[
ProjectPermissionSecretScanningDataSourceActions.Read,
@@ -526,6 +553,7 @@ const buildViewerPermissionRules = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
can(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs);
can(ProjectPermissionPkiSyncActions.Read, ProjectPermissionSub.PkiSyncs);
can(ProjectPermissionCommitsActions.Read, ProjectPermissionSub.Commits);
can(
@@ -120,6 +120,16 @@ export enum ProjectPermissionSecretSyncActions {
RemoveSecrets = "remove-secrets"
}
export enum ProjectPermissionPkiSyncActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
SyncCertificates = "sync-certificates",
ImportCertificates = "import-certificates",
RemoveCertificates = "remove-certificates"
}
export enum ProjectPermissionSecretRotationActions {
Read = "read",
ReadGeneratedCredentials = "read-generated-credentials",
@@ -212,6 +222,7 @@ export enum ProjectPermissionSub {
Kms = "kms",
Cmek = "cmek",
SecretSyncs = "secret-syncs",
PkiSyncs = "pki-syncs",
Kmip = "kmip",
SecretScanningDataSources = "secret-scanning-data-sources",
SecretScanningFindings = "secret-scanning-findings",
@@ -244,6 +255,10 @@ export type SecretSyncSubjectFields = {
secretPath: string;
};
export type PkiSyncSubjectFields = {
subscriberName: string;
};
export type DynamicSecretSubjectFields = {
environment: string;
secretPath: string;
@@ -308,6 +323,10 @@ export type ProjectPermissionSet =
ProjectPermissionSecretSyncActions,
ProjectPermissionSub.SecretSyncs | (ForcedSubject<ProjectPermissionSub.SecretSyncs> & SecretSyncSubjectFields)
]
| [
ProjectPermissionPkiSyncActions,
ProjectPermissionSub.PkiSyncs | (ForcedSubject<ProjectPermissionSub.PkiSyncs> & PkiSyncSubjectFields)
]
| [
ProjectPermissionActions,
(
@@ -480,6 +499,22 @@ const SecretSyncConditionV2Schema = z
})
.partial();
const PkiSyncConditionSchema = z
.object({
subscriberName: z.union([
z.string(),
z
.object({
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
})
.partial()
])
})
.partial();
const SecretImportConditionSchema = z
.object({
environment: z.union([
@@ -943,6 +978,16 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
"When specified, only matching conditions will be allowed to access given resource."
).optional()
}),
z.object({
subject: z.literal(ProjectPermissionSub.PkiSyncs).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionPkiSyncActions).describe(
"Describe what action an entity can take."
),
conditions: PkiSyncConditionSchema.describe(
"When specified, only matching conditions will be allowed to access given resource."
).optional()
}),
z.object({
subject: z.literal(ProjectPermissionSub.SecretEvents).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
@@ -284,7 +284,8 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
db.ref("version").withSchema(TableName.SecretVersionV2).as("secVerVersion"),
db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"),
db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"),
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment")
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment"),
db.ref("skipMultilineEncoding").withSchema(TableName.SecretVersionV2).as("secVerSkipMultilineEncoding")
)
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
@@ -326,14 +327,22 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
{
key: "secretVersion",
label: "secretVersion" as const,
mapper: ({ secretVersion, secVerVersion, secVerKey, secVerValue, secVerComment }) =>
mapper: ({
secretVersion,
secVerVersion,
secVerKey,
secVerValue,
secVerComment,
secVerSkipMultilineEncoding
}) =>
secretVersion
? {
version: secVerVersion,
id: secretVersion,
key: secVerKey,
encryptedValue: secVerValue,
encryptedComment: secVerComment
encryptedComment: secVerComment,
skipMultilineEncoding: secVerSkipMultilineEncoding
}
: undefined,
childrenMapper: [
@@ -337,12 +337,17 @@ export const secretApprovalRequestServiceFactory = ({
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
: el.secret && el.secret.isRotatedSecret
? undefined
: el.encryptedValue
: el.encryptedValue !== undefined && el.encryptedValue !== null
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
: "",
secretComment: el.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
: "",
: undefined,
secretComment:
el.encryptedComment !== undefined && el.encryptedComment !== null
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
: undefined,
skipMultilineEncoding:
el.skipMultilineEncoding !== undefined && el.skipMultilineEncoding !== null
? el.skipMultilineEncoding
: undefined,
secret: el.secret
? {
secretKey: el.secret.key,
@@ -394,7 +399,8 @@ export const secretApprovalRequestServiceFactory = ({
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
: "",
tags: el.secretVersion.tags,
secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO
secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO,
skipMultilineEncoding: el.secretVersion.skipMultilineEncoding
}
: undefined
}));
@@ -733,9 +739,9 @@ export const secretApprovalRequestServiceFactory = ({
tx,
inputSecrets: secretUpdationCommits.map((el) => {
const encryptedValue =
!el.secret?.isRotatedSecret && typeof el.encryptedValue !== "undefined"
!el.secret?.isRotatedSecret && el.encryptedValue !== null && el.encryptedValue !== undefined
? {
encryptedValue: el.encryptedValue as Buffer,
encryptedValue: el.encryptedValue,
references: el.encryptedValue
? getAllSecretReferencesV2Bridge(
secretManagerDecryptor({
@@ -749,9 +755,9 @@ export const secretApprovalRequestServiceFactory = ({
filter: { id: el.secretId as string, type: SecretType.Shared },
data: {
reminderRepeatDays: el.reminderRepeatDays,
encryptedComment: el.encryptedComment,
encryptedComment: el.encryptedComment !== null ? el.encryptedComment : undefined,
reminderNote: el.reminderNote,
skipMultilineEncoding: el.skipMultilineEncoding,
skipMultilineEncoding: el.skipMultilineEncoding !== null ? el.skipMultilineEncoding : undefined,
key: el.key,
tags: el?.tags.map(({ id }) => id),
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
@@ -1633,11 +1639,13 @@ export const secretApprovalRequestServiceFactory = ({
key: newSecretName || secretKey,
encryptedComment: setKnexStringValue(
secretComment,
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob,
true // scott: we need to encrypt empty string on update to differentiate not updating comment vs clearing comment
),
encryptedValue: setKnexStringValue(
secretValue,
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob,
true // scott: we need to encrypt empty string on update to differentiate not updating value vs clearing value
),
reminderRepeatDays,
reminderNote,