72ce77864e
## Summary Adds a daily Enterprise-only cron that rotates the current ES256 JWT signing key once it has been current for `SIGNING_KEY_ROTATION_DAYS`. Manual rotation from the admin panel is unaffected. ### Behaviour - `SIGNING_KEY_ROTATION_DAYS` is **opt-in**: when unset, the cron is a no-op. - Rotation flips `isCurrent` and clears the previous key's `privateKey` in the same transaction, then inserts the new `isCurrent=true` row. - The previous key's row is kept (`revokedAt` stays `null`) so its `publicKey` can keep verifying tokens it signed until they expire; only the encrypted `privateKey` is wiped since it can no longer be used to sign. - **No auto-revocation** — revoking a key remains a manual admin action, reserved for leak / emergency response. - The cron is also a no-op when `EnterprisePlanService.isValid()` is `false`. ### Wiring - `JwtKeyManagerService.rotateCurrent()` - `SigningKeyRotationService.rotateIfDue()` (reads `SIGNING_KEY_ROTATION_DAYS`, skips when unset) - `RotateSigningKeysCronJob` (Enterprise-gated, rethrows on failure) registered in `JwtModule` - `RotateSigningKeysCronCommand` registered with `cron:register:all` - `ROTATE_SIGNING_KEYS_CRON_PATTERN = '15 3 * * *'` (daily, no-op until threshold) Operator documentation lives in #20611 (docs PR).
203 lines
9.9 KiB
TypeScript
203 lines
9.9 KiB
TypeScript
import { Logger } from '@nestjs/common';
|
|
|
|
import { Command, CommandRunner } from 'nest-commander';
|
|
|
|
import { MarketplaceCatalogSyncCronCommand } from 'src/engine/core-modules/application/application-marketplace/crons/commands/marketplace-catalog-sync.cron.command';
|
|
import { StaleRegistrationCleanupCronCommand } from 'src/engine/core-modules/application/application-oauth/stale-registration-cleanup/commands/stale-registration-cleanup.cron.command';
|
|
import { ApplicationVersionCheckCronCommand } from 'src/engine/core-modules/application/application-upgrade/crons/commands/application-version-check.cron.command';
|
|
import { EnterpriseKeyValidationCronCommand } from 'src/engine/core-modules/enterprise/cron/command/enterprise-key-validation.cron.command';
|
|
import { EventLogCleanupCronCommand } from 'src/engine/core-modules/event-logs/cleanup/commands/event-log-cleanup.cron.command';
|
|
import { RotateSigningKeysCronCommand } from 'src/engine/core-modules/jwt/crons/commands/rotate-signing-keys.cron.command';
|
|
import { CronTriggerCronCommand } from 'src/engine/core-modules/logic-function/logic-function-trigger/triggers/cron/cron-trigger.cron.command';
|
|
import { CheckPublicDomainsValidRecordsCronCommand } from 'src/engine/core-modules/public-domain/crons/commands/check-public-domains-valid-records.cron.command';
|
|
import { CheckCustomDomainValidRecordsCronCommand } from 'src/engine/core-modules/workspace/crons/commands/check-custom-domain-valid-records.cron.command';
|
|
import { TrashCleanupCronCommand } from 'src/engine/trash-cleanup/commands/trash-cleanup.cron.command';
|
|
import { CleanOnboardingWorkspacesCronCommand } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-onboarding-workspaces.cron.command';
|
|
import { CleanSuspendedWorkspacesCronCommand } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.cron.command';
|
|
import { CalendarEventListFetchCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-event-list-fetch.cron.command';
|
|
import { CalendarEventsImportCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-import.cron.command';
|
|
import { CalendarOngoingStaleCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-ongoing-stale.cron.command';
|
|
import { CalendarRelaunchFailedCalendarChannelsCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-relaunch-failed-calendar-channels.cron.command';
|
|
import { MessagingMessageListFetchCronCommand } from 'src/modules/messaging/message-import-manager/crons/commands/messaging-message-list-fetch.cron.command';
|
|
import { MessagingMessagesImportCronCommand } from 'src/modules/messaging/message-import-manager/crons/commands/messaging-messages-import.cron.command';
|
|
import { MessagingOngoingStaleCronCommand } from 'src/modules/messaging/message-import-manager/crons/commands/messaging-ongoing-stale.cron.command';
|
|
import { MessagingRelaunchFailedMessageChannelsCronCommand } from 'src/modules/messaging/message-import-manager/crons/commands/messaging-relaunch-failed-message-channels.cron.command';
|
|
import { WorkflowCleanWorkflowRunsCronCommand } from 'src/modules/workflow/workflow-runner/workflow-run-queue/cron/command/workflow-clean-workflow-runs.cron.command';
|
|
import { WorkflowHandleStaledRunsCronCommand } from 'src/modules/workflow/workflow-runner/workflow-run-queue/cron/command/workflow-handle-staled-runs.cron.command';
|
|
import { WorkflowRunEnqueueCronCommand } from 'src/modules/workflow/workflow-runner/workflow-run-queue/cron/command/workflow-run-enqueue.cron.command';
|
|
import { WorkflowCronTriggerCronCommand } from 'src/modules/workflow/workflow-trigger/automated-trigger/crons/commands/workflow-cron-trigger.cron.command';
|
|
|
|
@Command({
|
|
name: 'cron:register:all',
|
|
description: 'Register all background sync cron jobs',
|
|
})
|
|
export class CronRegisterAllCommand extends CommandRunner {
|
|
private readonly logger = new Logger(CronRegisterAllCommand.name);
|
|
|
|
constructor(
|
|
private readonly messagingMessagesImportCronCommand: MessagingMessagesImportCronCommand,
|
|
private readonly messagingMessageListFetchCronCommand: MessagingMessageListFetchCronCommand,
|
|
private readonly messagingOngoingStaleCronCommand: MessagingOngoingStaleCronCommand,
|
|
private readonly messagingRelaunchFailedMessageChannelsCronCommand: MessagingRelaunchFailedMessageChannelsCronCommand,
|
|
|
|
private readonly calendarEventListFetchCronCommand: CalendarEventListFetchCronCommand,
|
|
private readonly calendarEventsImportCronCommand: CalendarEventsImportCronCommand,
|
|
private readonly calendarOngoingStaleCronCommand: CalendarOngoingStaleCronCommand,
|
|
private readonly calendarRelaunchFailedCalendarChannelsCronCommand: CalendarRelaunchFailedCalendarChannelsCronCommand,
|
|
|
|
private readonly workflowCronTriggerCronCommand: WorkflowCronTriggerCronCommand,
|
|
private readonly workflowRunEnqueueCronCommand: WorkflowRunEnqueueCronCommand,
|
|
private readonly workflowHandleStaledRunsCronCommand: WorkflowHandleStaledRunsCronCommand,
|
|
private readonly workflowCleanWorkflowRunsCronCommand: WorkflowCleanWorkflowRunsCronCommand,
|
|
|
|
private readonly checkCustomDomainValidRecordsCronCommand: CheckCustomDomainValidRecordsCronCommand,
|
|
private readonly checkPublicDomainsValidRecordsCronCommand: CheckPublicDomainsValidRecordsCronCommand,
|
|
private readonly cronTriggerCronCommand: CronTriggerCronCommand,
|
|
private readonly cleanSuspendedWorkspacesCronCommand: CleanSuspendedWorkspacesCronCommand,
|
|
private readonly cleanOnboardingWorkspacesCronCommand: CleanOnboardingWorkspacesCronCommand,
|
|
private readonly trashCleanupCronCommand: TrashCleanupCronCommand,
|
|
private readonly eventLogCleanupCronCommand: EventLogCleanupCronCommand,
|
|
private readonly enterpriseKeyValidationCronCommand: EnterpriseKeyValidationCronCommand,
|
|
private readonly rotateSigningKeysCronCommand: RotateSigningKeysCronCommand,
|
|
private readonly marketplaceCatalogSyncCronCommand: MarketplaceCatalogSyncCronCommand,
|
|
private readonly applicationVersionCheckCronCommand: ApplicationVersionCheckCronCommand,
|
|
private readonly staleRegistrationCleanupCronCommand: StaleRegistrationCleanupCronCommand,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
async run(): Promise<void> {
|
|
this.logger.log('Registering all background sync cron jobs...');
|
|
|
|
const allCommands = [
|
|
{
|
|
name: 'MessagingMessagesImport',
|
|
command: this.messagingMessagesImportCronCommand,
|
|
},
|
|
{
|
|
name: 'MessagingMessageListFetch',
|
|
command: this.messagingMessageListFetchCronCommand,
|
|
},
|
|
{
|
|
name: 'MessagingOngoingStale',
|
|
command: this.messagingOngoingStaleCronCommand,
|
|
},
|
|
{
|
|
name: 'MessagingRelaunchFailedMessageChannels',
|
|
command: this.messagingRelaunchFailedMessageChannelsCronCommand,
|
|
},
|
|
{
|
|
name: 'CalendarEventListFetch',
|
|
command: this.calendarEventListFetchCronCommand,
|
|
},
|
|
{
|
|
name: 'CalendarEventsImport',
|
|
command: this.calendarEventsImportCronCommand,
|
|
},
|
|
{
|
|
name: 'CalendarOngoingStale',
|
|
command: this.calendarOngoingStaleCronCommand,
|
|
},
|
|
{
|
|
name: 'CalendarRelaunchFailedCalendarChannels',
|
|
command: this.calendarRelaunchFailedCalendarChannelsCronCommand,
|
|
},
|
|
{
|
|
name: 'CheckCustomDomainValidRecords',
|
|
command: this.checkCustomDomainValidRecordsCronCommand,
|
|
},
|
|
{
|
|
name: 'CheckPublicDomainsValidRecords',
|
|
command: this.checkPublicDomainsValidRecordsCronCommand,
|
|
},
|
|
{
|
|
name: 'WorkflowCronTrigger',
|
|
command: this.workflowCronTriggerCronCommand,
|
|
},
|
|
{
|
|
name: 'WorkflowRunEnqueue',
|
|
command: this.workflowRunEnqueueCronCommand,
|
|
},
|
|
{
|
|
name: 'WorkflowHandleStaledRuns',
|
|
command: this.workflowHandleStaledRunsCronCommand,
|
|
},
|
|
{
|
|
name: 'WorkflowCleanWorkflowRuns',
|
|
command: this.workflowCleanWorkflowRunsCronCommand,
|
|
},
|
|
{
|
|
name: 'CronTrigger',
|
|
command: this.cronTriggerCronCommand,
|
|
},
|
|
{
|
|
name: 'CleanSuspendedWorkspaces',
|
|
command: this.cleanSuspendedWorkspacesCronCommand,
|
|
},
|
|
{
|
|
name: 'CleanOnboardingWorkspaces',
|
|
command: this.cleanOnboardingWorkspacesCronCommand,
|
|
},
|
|
{
|
|
name: 'TrashCleanup',
|
|
command: this.trashCleanupCronCommand,
|
|
},
|
|
{
|
|
name: 'EventLogCleanup',
|
|
command: this.eventLogCleanupCronCommand,
|
|
},
|
|
{
|
|
name: 'MarketplaceCatalogSync',
|
|
command: this.marketplaceCatalogSyncCronCommand,
|
|
},
|
|
{
|
|
name: 'ApplicationVersionCheck',
|
|
command: this.applicationVersionCheckCronCommand,
|
|
},
|
|
{
|
|
name: 'EnterpriseKeyValidation',
|
|
command: this.enterpriseKeyValidationCronCommand,
|
|
},
|
|
{
|
|
name: 'RotateSigningKeys',
|
|
command: this.rotateSigningKeysCronCommand,
|
|
},
|
|
{
|
|
name: 'StaleRegistrationCleanup',
|
|
command: this.staleRegistrationCleanupCronCommand,
|
|
},
|
|
];
|
|
|
|
let successCount = 0;
|
|
let failureCount = 0;
|
|
const failures: string[] = [];
|
|
const successes: string[] = [];
|
|
|
|
for (const { name, command } of allCommands) {
|
|
try {
|
|
this.logger.log(`Registering ${name} cron job...`);
|
|
await command.run();
|
|
this.logger.log(`Successfully registered ${name} cron job`);
|
|
successCount++;
|
|
successes.push(name);
|
|
} catch (error) {
|
|
this.logger.error(`Failed to register ${name} cron job:`, error);
|
|
failureCount++;
|
|
failures.push(name);
|
|
}
|
|
}
|
|
|
|
this.logger.log(
|
|
`Cron job registration completed: ${successCount} successful, ${failureCount} failed`,
|
|
);
|
|
|
|
if (failures.length > 0) {
|
|
this.logger.warn(`Failed commands: ${failures.join(', ')}`);
|
|
}
|
|
|
|
if (successCount > 0) {
|
|
this.logger.log(`Successful commands: ${successes.join(', ')}`);
|
|
}
|
|
}
|
|
}
|