Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions backend/src/@types/fastify.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import { TKmipServiceFactory } from "@app/ee/services/kmip/kmip-service";
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-service";
import { TPamAccountServiceFactory } from "@app/ee/services/pam-account/pam-account-service";
import { TPamFolderServiceFactory } from "@app/ee/services/pam-folder/pam-folder-service";
import { TPamResourceServiceFactory } from "@app/ee/services/pam-resource/pam-resource-service";
import { TPamSessionServiceFactory } from "@app/ee/services/pam-session/pam-session-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { TPitServiceFactory } from "@app/ee/services/pit/pit-service";
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-types";
Expand Down Expand Up @@ -315,6 +319,10 @@ declare module "fastify" {
identityAuthTemplate: TIdentityAuthTemplateServiceFactory;
notification: TNotificationServiceFactory;
offlineUsageReport: TOfflineUsageReportServiceFactory;
pamFolder: TPamFolderServiceFactory;
pamResource: TPamResourceServiceFactory;
pamAccount: TPamAccountServiceFactory;
pamSession: TPamSessionServiceFactory;
upgradePath: TUpgradePathService;
};
// this is exclusive use for middlewares in which we need to inject data
Expand Down
8 changes: 8 additions & 0 deletions backend/src/@types/knex.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,10 @@ import {
TMicrosoftTeamsIntegrationsInsert,
TMicrosoftTeamsIntegrationsUpdate
} from "@app/db/schemas/microsoft-teams-integrations";
import { TPamAccounts, TPamAccountsInsert, TPamAccountsUpdate } from "@app/db/schemas/pam-accounts";
import { TPamFolders, TPamFoldersInsert, TPamFoldersUpdate } from "@app/db/schemas/pam-folders";
import { TPamResources, TPamResourcesInsert, TPamResourcesUpdate } from "@app/db/schemas/pam-resources";
import { TPamSessions, TPamSessionsInsert, TPamSessionsUpdate } from "@app/db/schemas/pam-sessions";
import {
TProjectMicrosoftTeamsConfigs,
TProjectMicrosoftTeamsConfigsInsert,
Expand Down Expand Up @@ -1308,5 +1312,9 @@ declare module "knex/types/tables" {
TKeyValueStoreInsert,
TKeyValueStoreUpdate
>;
[TableName.PamFolder]: KnexOriginal.CompositeTableType<TPamFolders, TPamFoldersInsert, TPamFoldersUpdate>;
[TableName.PamResource]: KnexOriginal.CompositeTableType<TPamResources, TPamResourcesInsert, TPamResourcesUpdate>;
[TableName.PamAccount]: KnexOriginal.CompositeTableType<TPamAccounts, TPamAccountsInsert, TPamAccountsUpdate>;
[TableName.PamSession]: KnexOriginal.CompositeTableType<TPamSessions, TPamSessionsInsert, TPamSessionsUpdate>;
}
}
165 changes: 165 additions & 0 deletions backend/src/db/migrations/20250917052037_pam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { Knex } from "knex";

import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";

export async function up(knex: Knex): Promise<void> {
// PAM Folders
if (!(await knex.schema.hasTable(TableName.PamFolder))) {
await knex.schema.createTable(TableName.PamFolder, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());

t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.index("projectId");

t.uuid("parentId").nullable();
t.foreign("parentId").references("id").inTable(TableName.PamFolder).onDelete("CASCADE");
t.index("parentId");

t.string("name").notNullable();
t.index("name");

// Enforce uniqueness for sub-folders
t.unique(["projectId", "parentId", "name"], {
indexName: "uidx_pam_folder_children_name",
predicate: knex.whereNotNull("parentId")
});

// Enforce uniqueness for root-level folders
t.unique(["projectId", "name"], {
indexName: "uidx_pam_folder_root_name",
predicate: knex.whereNull("parentId")
});

t.text("description").nullable();

t.timestamps(true, true, true);
});
}

// PAM Resources
if (!(await knex.schema.hasTable(TableName.PamResource))) {
await knex.schema.createTable(TableName.PamResource, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());

t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.index("projectId");

t.string("name").notNullable();
t.index("name");

t.uuid("gatewayId").notNullable();
t.foreign("gatewayId").references("id").inTable(TableName.GatewayV2);
t.index("gatewayId");

t.string("resourceType").notNullable();
t.index("resourceType");

t.binary("encryptedConnectionDetails").notNullable();

t.timestamps(true, true, true);
});
}

// PAM Accounts
if (!(await knex.schema.hasTable(TableName.PamAccount))) {
await knex.schema.createTable(TableName.PamAccount, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());

t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.index("projectId");

t.uuid("folderId").nullable();
t.foreign("folderId").references("id").inTable(TableName.PamFolder).onDelete("CASCADE");
t.index("folderId");

t.uuid("resourceId").notNullable();
t.foreign("resourceId").references("id").inTable(TableName.PamResource);
t.index("resourceId");

t.string("name").notNullable();
t.index("name");

// Enforce uniqueness for folders
t.unique(["projectId", "folderId", "name"], {
indexName: "uidx_pam_account_children_name",
predicate: knex.whereNotNull("folderId")
});

// Enforce uniqueness for root-level
t.unique(["projectId", "name"], {
indexName: "uidx_pam_account_root_name",
predicate: knex.whereNull("folderId")
});

t.text("description").nullable();
t.binary("encryptedCredentials").notNullable();

t.timestamps(true, true, true);
});
}

// PAM Sessions
if (!(await knex.schema.hasTable(TableName.PamSession))) {
await knex.schema.createTable(TableName.PamSession, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());

t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.index("projectId");

t.uuid("accountId").nullable();
t.foreign("accountId").references("id").inTable(TableName.PamAccount).onDelete("SET NULL");
t.index("accountId");

// To be used in the event of an account deletion
t.string("resourceType").notNullable();
t.string("resourceName").notNullable();
t.string("accountName").notNullable();

t.uuid("userId").nullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("SET NULL");
t.index("userId");

// To be used in the event of user deletion
t.string("actorName").notNullable();
t.string("actorEmail").notNullable();

t.string("actorIp").notNullable();
t.string("actorUserAgent").notNullable();

t.string("status").notNullable();
t.index("status");

t.binary("encryptedLogsBlob").nullable();

t.datetime("expiresAt").notNullable();

t.datetime("startedAt").nullable(); // Not when the row is created, but when the end-to-end connection between user and resource is established
t.datetime("endedAt").nullable();
t.index(["startedAt", "endedAt"]);

t.timestamps(true, true, true);
});
}

await createOnUpdateTrigger(knex, TableName.PamFolder);
await createOnUpdateTrigger(knex, TableName.PamResource);
await createOnUpdateTrigger(knex, TableName.PamAccount);
await createOnUpdateTrigger(knex, TableName.PamSession);
}

export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.PamSession);
await knex.schema.dropTableIfExists(TableName.PamAccount);
await knex.schema.dropTableIfExists(TableName.PamResource);
await knex.schema.dropTableIfExists(TableName.PamFolder);

await dropOnUpdateTrigger(knex, TableName.PamSession);
await dropOnUpdateTrigger(knex, TableName.PamAccount);
await dropOnUpdateTrigger(knex, TableName.PamResource);
await dropOnUpdateTrigger(knex, TableName.PamFolder);
}
19 changes: 19 additions & 0 deletions backend/src/db/migrations/20251002113756_add-gateway-pam-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Knex } from "knex";

import { TableName } from "../schemas";

export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.GatewayV2, "encryptedPamSessionKey"))) {
await knex.schema.alterTable(TableName.GatewayV2, (t) => {
t.binary("encryptedPamSessionKey");
});
}
}

export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.GatewayV2, "encryptedPamSessionKey")) {
await knex.schema.alterTable(TableName.GatewayV2, (t) => {
t.dropColumn("encryptedPamSessionKey");
});
}
}
5 changes: 4 additions & 1 deletion backend/src/db/schemas/gateways-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import { z } from "zod";

import { zodBuffer } from "@app/lib/zod";

import { TImmutableDBKeys } from "./models";

export const GatewaysV2Schema = z.object({
Expand All @@ -15,7 +17,8 @@ export const GatewaysV2Schema = z.object({
identityId: z.string().uuid(),
relayId: z.string().uuid().nullable().optional(),
name: z.string(),
heartbeat: z.date().nullable().optional()
heartbeat: z.date().nullable().optional(),
encryptedPamSessionKey: zodBuffer.nullable().optional()
});

export type TGatewaysV2 = z.infer<typeof GatewaysV2Schema>;
Expand Down
4 changes: 4 additions & 0 deletions backend/src/db/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export * from "./org-memberships";
export * from "./org-relay-config";
export * from "./org-roles";
export * from "./organizations";
export * from "./pam-accounts";
export * from "./pam-folders";
export * from "./pam-resources";
export * from "./pam-sessions";
export * from "./pki-alerts";
export * from "./pki-collection-items";
export * from "./pki-collections";
Expand Down
12 changes: 10 additions & 2 deletions backend/src/db/schemas/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,13 @@ export enum TableName {
Relay = "relays",
GatewayV2 = "gateways_v2",

KeyValueStore = "key_value_store"
KeyValueStore = "key_value_store",

// PAM
PamFolder = "pam_folders",
PamResource = "pam_resources",
PamAccount = "pam_accounts",
PamSession = "pam_sessions"
}

export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
Expand Down Expand Up @@ -281,7 +287,8 @@ export enum ProjectType {
CertificateManager = "cert-manager",
KMS = "kms",
SSH = "ssh",
SecretScanning = "secret-scanning"
SecretScanning = "secret-scanning",
PAM = "pam"
}

export enum ActionProjectType {
Expand All @@ -290,6 +297,7 @@ export enum ActionProjectType {
KMS = ProjectType.KMS,
SSH = ProjectType.SSH,
SecretScanning = ProjectType.SecretScanning,
PAM = ProjectType.PAM,
// project operations that happen on all types
Any = "any"
}
Expand Down
26 changes: 26 additions & 0 deletions backend/src/db/schemas/pam-accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.

import { z } from "zod";

import { zodBuffer } from "@app/lib/zod";

import { TImmutableDBKeys } from "./models";

export const PamAccountsSchema = z.object({
id: z.string().uuid(),
projectId: z.string(),
folderId: z.string().uuid().nullable().optional(),
resourceId: z.string().uuid(),
name: z.string(),
description: z.string().nullable().optional(),
encryptedCredentials: zodBuffer,
createdAt: z.date(),
updatedAt: z.date()
});

export type TPamAccounts = z.infer<typeof PamAccountsSchema>;
export type TPamAccountsInsert = Omit<z.input<typeof PamAccountsSchema>, TImmutableDBKeys>;
export type TPamAccountsUpdate = Partial<Omit<z.input<typeof PamAccountsSchema>, TImmutableDBKeys>>;
22 changes: 22 additions & 0 deletions backend/src/db/schemas/pam-folders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.

import { z } from "zod";

import { TImmutableDBKeys } from "./models";

export const PamFoldersSchema = z.object({
id: z.string().uuid(),
projectId: z.string(),
parentId: z.string().uuid().nullable().optional(),
name: z.string(),
description: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});

export type TPamFolders = z.infer<typeof PamFoldersSchema>;
export type TPamFoldersInsert = Omit<z.input<typeof PamFoldersSchema>, TImmutableDBKeys>;
export type TPamFoldersUpdate = Partial<Omit<z.input<typeof PamFoldersSchema>, TImmutableDBKeys>>;
25 changes: 25 additions & 0 deletions backend/src/db/schemas/pam-resources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.

import { z } from "zod";

import { zodBuffer } from "@app/lib/zod";

import { TImmutableDBKeys } from "./models";

export const PamResourcesSchema = z.object({
id: z.string().uuid(),
projectId: z.string(),
name: z.string(),
gatewayId: z.string().uuid(),
resourceType: z.string(),
encryptedConnectionDetails: zodBuffer,
createdAt: z.date(),
updatedAt: z.date()
});

export type TPamResources = z.infer<typeof PamResourcesSchema>;
export type TPamResourcesInsert = Omit<z.input<typeof PamResourcesSchema>, TImmutableDBKeys>;
export type TPamResourcesUpdate = Partial<Omit<z.input<typeof PamResourcesSchema>, TImmutableDBKeys>>;
Loading
Loading