mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-31 15:06:42 +00:00
Configure connection log retention time
This commit is contained in:
@@ -2398,6 +2398,8 @@
|
|||||||
"logRetentionAccessDescription": "How long to retain access logs",
|
"logRetentionAccessDescription": "How long to retain access logs",
|
||||||
"logRetentionActionLabel": "Action Log Retention",
|
"logRetentionActionLabel": "Action Log Retention",
|
||||||
"logRetentionActionDescription": "How long to retain action logs",
|
"logRetentionActionDescription": "How long to retain action logs",
|
||||||
|
"logRetentionConnectionLabel": "Connection Log Retention",
|
||||||
|
"logRetentionConnectionDescription": "How long to retain connection logs",
|
||||||
"logRetentionDisabled": "Disabled",
|
"logRetentionDisabled": "Disabled",
|
||||||
"logRetention3Days": "3 days",
|
"logRetention3Days": "3 days",
|
||||||
"logRetention7Days": "7 days",
|
"logRetention7Days": "7 days",
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ export const accessAuditLog = pgTable(
|
|||||||
actor: varchar("actor", { length: 255 }),
|
actor: varchar("actor", { length: 255 }),
|
||||||
actorId: varchar("actorId", { length: 255 }),
|
actorId: varchar("actorId", { length: 255 }),
|
||||||
resourceId: integer("resourceId"),
|
resourceId: integer("resourceId"),
|
||||||
|
siteResourceId: integer("siteResourceId"),
|
||||||
ip: varchar("ip", { length: 45 }),
|
ip: varchar("ip", { length: 45 }),
|
||||||
type: varchar("type", { length: 100 }).notNull(),
|
type: varchar("type", { length: 100 }).notNull(),
|
||||||
action: boolean("action").notNull(),
|
action: boolean("action").notNull(),
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ export const accessAuditLog = sqliteTable(
|
|||||||
actor: text("actor"),
|
actor: text("actor"),
|
||||||
actorId: text("actorId"),
|
actorId: text("actorId"),
|
||||||
resourceId: integer("resourceId"),
|
resourceId: integer("resourceId"),
|
||||||
|
siteResourceId: integer("siteResourceId"),
|
||||||
ip: text("ip"),
|
ip: text("ip"),
|
||||||
location: text("location"),
|
location: text("location"),
|
||||||
type: text("type").notNull(),
|
type: text("type").notNull(),
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export async function logAccessAudit(data: {
|
|||||||
type: string;
|
type: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
resourceId?: number;
|
resourceId?: number;
|
||||||
|
siteResourceId?: number;
|
||||||
user?: { username: string; userId: string };
|
user?: { username: string; userId: string };
|
||||||
apiKey?: { name: string | null; apiKeyId: string };
|
apiKey?: { name: string | null; apiKeyId: string };
|
||||||
metadata?: any;
|
metadata?: any;
|
||||||
@@ -134,6 +135,7 @@ export async function logAccessAudit(data: {
|
|||||||
type: data.type,
|
type: data.type,
|
||||||
metadata,
|
metadata,
|
||||||
resourceId: data.resourceId,
|
resourceId: data.resourceId,
|
||||||
|
siteResourceId: data.siteResourceId,
|
||||||
userAgent: data.userAgent,
|
userAgent: data.userAgent,
|
||||||
ip: clientIp,
|
ip: clientIp,
|
||||||
location: countryCode
|
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
|
// Apply updates if needed
|
||||||
if (needsUpdate) {
|
if (needsUpdate) {
|
||||||
await db.update(orgs).set(updates).where(eq(orgs.orgId, orgId));
|
await db.update(orgs).set(updates).where(eq(orgs.orgId, orgId));
|
||||||
@@ -262,6 +274,10 @@ async function disableFeature(
|
|||||||
await disableActionLogs(orgId);
|
await disableActionLogs(orgId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TierFeature.ConnectionLogs:
|
||||||
|
await disableConnectionLogs(orgId);
|
||||||
|
break;
|
||||||
|
|
||||||
case TierFeature.RotateCredentials:
|
case TierFeature.RotateCredentials:
|
||||||
await disableRotateCredentials(orgId);
|
await disableRotateCredentials(orgId);
|
||||||
break;
|
break;
|
||||||
@@ -458,6 +474,15 @@ async function disableActionLogs(orgId: string): Promise<void> {
|
|||||||
logger.info(`Disabled action logs for org ${orgId}`);
|
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 disableRotateCredentials(orgId: string): Promise<void> {}
|
||||||
|
|
||||||
async function disableMaintencePage(orgId: string): Promise<void> {
|
async function disableMaintencePage(orgId: string): Promise<void> {
|
||||||
|
|||||||
@@ -488,7 +488,7 @@ export async function signSshKey(
|
|||||||
action: true,
|
action: true,
|
||||||
type: "ssh",
|
type: "ssh",
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
resourceId: resource.siteResourceId,
|
siteResourceId: resource.siteResourceId,
|
||||||
user: req.user
|
user: req.user
|
||||||
? { username: req.user.username ?? "", userId: req.user.userId }
|
? { username: req.user.username ?? "", userId: req.user.userId }
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ const updateOrgBodySchema = z
|
|||||||
.min(build === "saas" ? 0 : -1)
|
.min(build === "saas" ? 0 : -1)
|
||||||
.optional(),
|
.optional(),
|
||||||
settingsLogRetentionDaysAction: z
|
settingsLogRetentionDaysAction: z
|
||||||
|
.number()
|
||||||
|
.min(build === "saas" ? 0 : -1)
|
||||||
|
.optional(),
|
||||||
|
settingsLogRetentionDaysConnection: z
|
||||||
.number()
|
.number()
|
||||||
.min(build === "saas" ? 0 : -1)
|
.min(build === "saas" ? 0 : -1)
|
||||||
.optional()
|
.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:
|
settingsLogRetentionDaysAccess:
|
||||||
parsedBody.data.settingsLogRetentionDaysAccess,
|
parsedBody.data.settingsLogRetentionDaysAccess,
|
||||||
settingsLogRetentionDaysAction:
|
settingsLogRetentionDaysAction:
|
||||||
parsedBody.data.settingsLogRetentionDaysAction
|
parsedBody.data.settingsLogRetentionDaysAction,
|
||||||
|
settingsLogRetentionDaysConnection:
|
||||||
|
parsedBody.data.settingsLogRetentionDaysConnection
|
||||||
})
|
})
|
||||||
.where(eq(orgs.orgId, orgId))
|
.where(eq(orgs.orgId, orgId))
|
||||||
.returning();
|
.returning();
|
||||||
@@ -197,6 +214,7 @@ export async function updateOrg(
|
|||||||
await cache.del(`org_${orgId}_retentionDays`);
|
await cache.del(`org_${orgId}_retentionDays`);
|
||||||
await cache.del(`org_${orgId}_actionDays`);
|
await cache.del(`org_${orgId}_actionDays`);
|
||||||
await cache.del(`org_${orgId}_accessDays`);
|
await cache.del(`org_${orgId}_accessDays`);
|
||||||
|
await cache.del(`org_${orgId}_connectionDays`);
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
data: updatedOrg[0],
|
data: updatedOrg[0],
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ const SecurityFormSchema = z.object({
|
|||||||
passwordExpiryDays: z.number().nullable().optional(),
|
passwordExpiryDays: z.number().nullable().optional(),
|
||||||
settingsLogRetentionDaysRequest: z.number(),
|
settingsLogRetentionDaysRequest: z.number(),
|
||||||
settingsLogRetentionDaysAccess: z.number(),
|
settingsLogRetentionDaysAccess: z.number(),
|
||||||
settingsLogRetentionDaysAction: z.number()
|
settingsLogRetentionDaysAction: z.number(),
|
||||||
|
settingsLogRetentionDaysConnection: z.number()
|
||||||
});
|
});
|
||||||
|
|
||||||
const LOG_RETENTION_OPTIONS = [
|
const LOG_RETENTION_OPTIONS = [
|
||||||
@@ -120,7 +121,8 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
|||||||
SecurityFormSchema.pick({
|
SecurityFormSchema.pick({
|
||||||
settingsLogRetentionDaysRequest: true,
|
settingsLogRetentionDaysRequest: true,
|
||||||
settingsLogRetentionDaysAccess: true,
|
settingsLogRetentionDaysAccess: true,
|
||||||
settingsLogRetentionDaysAction: true
|
settingsLogRetentionDaysAction: true,
|
||||||
|
settingsLogRetentionDaysConnection: true
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -129,7 +131,9 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
|||||||
settingsLogRetentionDaysAccess:
|
settingsLogRetentionDaysAccess:
|
||||||
org.settingsLogRetentionDaysAccess ?? 15,
|
org.settingsLogRetentionDaysAccess ?? 15,
|
||||||
settingsLogRetentionDaysAction:
|
settingsLogRetentionDaysAction:
|
||||||
org.settingsLogRetentionDaysAction ?? 15
|
org.settingsLogRetentionDaysAction ?? 15,
|
||||||
|
settingsLogRetentionDaysConnection:
|
||||||
|
org.settingsLogRetentionDaysConnection ?? 15
|
||||||
},
|
},
|
||||||
mode: "onChange"
|
mode: "onChange"
|
||||||
});
|
});
|
||||||
@@ -155,7 +159,9 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
|||||||
settingsLogRetentionDaysAccess:
|
settingsLogRetentionDaysAccess:
|
||||||
data.settingsLogRetentionDaysAccess,
|
data.settingsLogRetentionDaysAccess,
|
||||||
settingsLogRetentionDaysAction:
|
settingsLogRetentionDaysAction:
|
||||||
data.settingsLogRetentionDaysAction
|
data.settingsLogRetentionDaysAction,
|
||||||
|
settingsLogRetentionDaysConnection:
|
||||||
|
data.settingsLogRetentionDaysConnection
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
// Update organization
|
// 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>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user