mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-30 22:46:40 +00:00
Configure connection log retention time
This commit is contained in:
@@ -2398,6 +2398,8 @@
|
||||
"logRetentionAccessDescription": "How long to retain access logs",
|
||||
"logRetentionActionLabel": "Action Log Retention",
|
||||
"logRetentionActionDescription": "How long to retain action logs",
|
||||
"logRetentionConnectionLabel": "Connection Log Retention",
|
||||
"logRetentionConnectionDescription": "How long to retain connection logs",
|
||||
"logRetentionDisabled": "Disabled",
|
||||
"logRetention3Days": "3 days",
|
||||
"logRetention7Days": "7 days",
|
||||
|
||||
@@ -291,6 +291,7 @@ export const accessAuditLog = pgTable(
|
||||
actor: varchar("actor", { length: 255 }),
|
||||
actorId: varchar("actorId", { length: 255 }),
|
||||
resourceId: integer("resourceId"),
|
||||
siteResourceId: integer("siteResourceId"),
|
||||
ip: varchar("ip", { length: 45 }),
|
||||
type: varchar("type", { length: 100 }).notNull(),
|
||||
action: boolean("action").notNull(),
|
||||
|
||||
@@ -279,6 +279,7 @@ export const accessAuditLog = sqliteTable(
|
||||
actor: text("actor"),
|
||||
actorId: text("actorId"),
|
||||
resourceId: integer("resourceId"),
|
||||
siteResourceId: integer("siteResourceId"),
|
||||
ip: text("ip"),
|
||||
location: text("location"),
|
||||
type: text("type").notNull(),
|
||||
|
||||
@@ -74,6 +74,7 @@ export async function logAccessAudit(data: {
|
||||
type: string;
|
||||
orgId: string;
|
||||
resourceId?: number;
|
||||
siteResourceId?: number;
|
||||
user?: { username: string; userId: string };
|
||||
apiKey?: { name: string | null; apiKeyId: string };
|
||||
metadata?: any;
|
||||
@@ -134,6 +135,7 @@ export async function logAccessAudit(data: {
|
||||
type: data.type,
|
||||
metadata,
|
||||
resourceId: data.resourceId,
|
||||
siteResourceId: data.siteResourceId,
|
||||
userAgent: data.userAgent,
|
||||
ip: clientIp,
|
||||
location: countryCode
|
||||
|
||||
@@ -120,6 +120,18 @@ async function capRetentionDays(
|
||||
);
|
||||
}
|
||||
|
||||
// Cap action log retention if it exceeds the limit
|
||||
if (
|
||||
org.settingsLogRetentionDaysConnection !== null &&
|
||||
org.settingsLogRetentionDaysConnection > maxRetentionDays
|
||||
) {
|
||||
updates.settingsLogRetentionDaysConnection = maxRetentionDays;
|
||||
needsUpdate = true;
|
||||
logger.info(
|
||||
`Capping connection log retention from ${org.settingsLogRetentionDaysConnection} to ${maxRetentionDays} days for org ${orgId}`
|
||||
);
|
||||
}
|
||||
|
||||
// Apply updates if needed
|
||||
if (needsUpdate) {
|
||||
await db.update(orgs).set(updates).where(eq(orgs.orgId, orgId));
|
||||
@@ -262,6 +274,10 @@ async function disableFeature(
|
||||
await disableActionLogs(orgId);
|
||||
break;
|
||||
|
||||
case TierFeature.ConnectionLogs:
|
||||
await disableConnectionLogs(orgId);
|
||||
break;
|
||||
|
||||
case TierFeature.RotateCredentials:
|
||||
await disableRotateCredentials(orgId);
|
||||
break;
|
||||
@@ -458,6 +474,15 @@ async function disableActionLogs(orgId: string): Promise<void> {
|
||||
logger.info(`Disabled action logs for org ${orgId}`);
|
||||
}
|
||||
|
||||
async function disableConnectionLogs(orgId: string): Promise<void> {
|
||||
await db
|
||||
.update(orgs)
|
||||
.set({ settingsLogRetentionDaysConnection: 0 })
|
||||
.where(eq(orgs.orgId, orgId));
|
||||
|
||||
logger.info(`Disabled connection logs for org ${orgId}`);
|
||||
}
|
||||
|
||||
async function disableRotateCredentials(orgId: string): Promise<void> {}
|
||||
|
||||
async function disableMaintencePage(orgId: string): Promise<void> {
|
||||
|
||||
@@ -488,7 +488,7 @@ export async function signSshKey(
|
||||
action: true,
|
||||
type: "ssh",
|
||||
orgId: orgId,
|
||||
resourceId: resource.siteResourceId,
|
||||
siteResourceId: resource.siteResourceId,
|
||||
user: req.user
|
||||
? { username: req.user.username ?? "", userId: req.user.userId }
|
||||
: undefined,
|
||||
|
||||
@@ -34,6 +34,10 @@ const updateOrgBodySchema = z
|
||||
.min(build === "saas" ? 0 : -1)
|
||||
.optional(),
|
||||
settingsLogRetentionDaysAction: z
|
||||
.number()
|
||||
.min(build === "saas" ? 0 : -1)
|
||||
.optional(),
|
||||
settingsLogRetentionDaysConnection: z
|
||||
.number()
|
||||
.min(build === "saas" ? 0 : -1)
|
||||
.optional()
|
||||
@@ -164,6 +168,17 @@ export async function updateOrg(
|
||||
)
|
||||
);
|
||||
}
|
||||
if (
|
||||
parsedBody.data.settingsLogRetentionDaysConnection !== undefined &&
|
||||
parsedBody.data.settingsLogRetentionDaysConnection > maxRetentionDays
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.FORBIDDEN,
|
||||
`You are not allowed to set log retention days greater than ${maxRetentionDays} with your current subscription`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +194,9 @@ export async function updateOrg(
|
||||
settingsLogRetentionDaysAccess:
|
||||
parsedBody.data.settingsLogRetentionDaysAccess,
|
||||
settingsLogRetentionDaysAction:
|
||||
parsedBody.data.settingsLogRetentionDaysAction
|
||||
parsedBody.data.settingsLogRetentionDaysAction,
|
||||
settingsLogRetentionDaysConnection:
|
||||
parsedBody.data.settingsLogRetentionDaysConnection
|
||||
})
|
||||
.where(eq(orgs.orgId, orgId))
|
||||
.returning();
|
||||
@@ -197,6 +214,7 @@ export async function updateOrg(
|
||||
await cache.del(`org_${orgId}_retentionDays`);
|
||||
await cache.del(`org_${orgId}_actionDays`);
|
||||
await cache.del(`org_${orgId}_accessDays`);
|
||||
await cache.del(`org_${orgId}_connectionDays`);
|
||||
|
||||
return response(res, {
|
||||
data: updatedOrg[0],
|
||||
|
||||
@@ -79,7 +79,8 @@ const SecurityFormSchema = z.object({
|
||||
passwordExpiryDays: z.number().nullable().optional(),
|
||||
settingsLogRetentionDaysRequest: z.number(),
|
||||
settingsLogRetentionDaysAccess: z.number(),
|
||||
settingsLogRetentionDaysAction: z.number()
|
||||
settingsLogRetentionDaysAction: z.number(),
|
||||
settingsLogRetentionDaysConnection: z.number()
|
||||
});
|
||||
|
||||
const LOG_RETENTION_OPTIONS = [
|
||||
@@ -120,7 +121,8 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
||||
SecurityFormSchema.pick({
|
||||
settingsLogRetentionDaysRequest: true,
|
||||
settingsLogRetentionDaysAccess: true,
|
||||
settingsLogRetentionDaysAction: true
|
||||
settingsLogRetentionDaysAction: true,
|
||||
settingsLogRetentionDaysConnection: true
|
||||
})
|
||||
),
|
||||
defaultValues: {
|
||||
@@ -129,7 +131,9 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
||||
settingsLogRetentionDaysAccess:
|
||||
org.settingsLogRetentionDaysAccess ?? 15,
|
||||
settingsLogRetentionDaysAction:
|
||||
org.settingsLogRetentionDaysAction ?? 15
|
||||
org.settingsLogRetentionDaysAction ?? 15,
|
||||
settingsLogRetentionDaysConnection:
|
||||
org.settingsLogRetentionDaysConnection ?? 15
|
||||
},
|
||||
mode: "onChange"
|
||||
});
|
||||
@@ -155,7 +159,9 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
||||
settingsLogRetentionDaysAccess:
|
||||
data.settingsLogRetentionDaysAccess,
|
||||
settingsLogRetentionDaysAction:
|
||||
data.settingsLogRetentionDaysAction
|
||||
data.settingsLogRetentionDaysAction,
|
||||
settingsLogRetentionDaysConnection:
|
||||
data.settingsLogRetentionDaysConnection
|
||||
} as any;
|
||||
|
||||
// Update organization
|
||||
@@ -473,6 +479,107 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="settingsLogRetentionDaysConnection"
|
||||
render={({ field }) => {
|
||||
const isDisabled = !isPaidUser(
|
||||
tierMatrix.connectionLogs
|
||||
);
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t(
|
||||
"logRetentionConnectionLabel"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
value={field.value.toString()}
|
||||
onValueChange={(
|
||||
value
|
||||
) => {
|
||||
if (
|
||||
!isDisabled
|
||||
) {
|
||||
field.onChange(
|
||||
parseInt(
|
||||
value,
|
||||
10
|
||||
)
|
||||
);
|
||||
}
|
||||
}}
|
||||
disabled={
|
||||
isDisabled
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"selectLogRetention"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LOG_RETENTION_OPTIONS.filter(
|
||||
(option) => {
|
||||
if (build != "saas") {
|
||||
return true;
|
||||
}
|
||||
|
||||
let maxDays: number;
|
||||
|
||||
if (!subscriptionTier) {
|
||||
// No tier
|
||||
maxDays = 3;
|
||||
} else if (subscriptionTier == "enterprise") {
|
||||
// Enterprise - no limit
|
||||
return true;
|
||||
} else if (subscriptionTier == "tier3") {
|
||||
maxDays = 90;
|
||||
} else if (subscriptionTier == "tier2") {
|
||||
maxDays = 30;
|
||||
} else if (subscriptionTier == "tier1") {
|
||||
maxDays = 7;
|
||||
} else {
|
||||
// Default to most restrictive
|
||||
maxDays = 3;
|
||||
}
|
||||
|
||||
// Filter out options that exceed the max
|
||||
// Special values: -1 (forever) and 9001 (end of year) should be filtered
|
||||
if (option.value < 0 || option.value > maxDays) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
).map(
|
||||
(
|
||||
option
|
||||
) => (
|
||||
<SelectItem
|
||||
key={
|
||||
option.value
|
||||
}
|
||||
value={option.value.toString()}
|
||||
>
|
||||
{t(
|
||||
option.label
|
||||
)}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user