mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 23:27:35 +00:00
misc: migrated to use multiple app connections
This commit is contained in:
Vendored
+7
-7
@@ -83,9 +83,6 @@ import {
|
|||||||
TExternalKms,
|
TExternalKms,
|
||||||
TExternalKmsInsert,
|
TExternalKmsInsert,
|
||||||
TExternalKmsUpdate,
|
TExternalKmsUpdate,
|
||||||
TExternalMigrationConfigs,
|
|
||||||
TExternalMigrationConfigsInsert,
|
|
||||||
TExternalMigrationConfigsUpdate,
|
|
||||||
TFolderCheckpointResources,
|
TFolderCheckpointResources,
|
||||||
TFolderCheckpointResourcesInsert,
|
TFolderCheckpointResourcesInsert,
|
||||||
TFolderCheckpointResourcesUpdate,
|
TFolderCheckpointResourcesUpdate,
|
||||||
@@ -521,6 +518,9 @@ import {
|
|||||||
TUsers,
|
TUsers,
|
||||||
TUsersInsert,
|
TUsersInsert,
|
||||||
TUsersUpdate,
|
TUsersUpdate,
|
||||||
|
TVaultExternalMigrationConfigs,
|
||||||
|
TVaultExternalMigrationConfigsInsert,
|
||||||
|
TVaultExternalMigrationConfigsUpdate,
|
||||||
TWebhooks,
|
TWebhooks,
|
||||||
TWebhooksInsert,
|
TWebhooksInsert,
|
||||||
TWebhooksUpdate,
|
TWebhooksUpdate,
|
||||||
@@ -1348,10 +1348,10 @@ declare module "knex/types/tables" {
|
|||||||
TAdditionalPrivilegesInsert,
|
TAdditionalPrivilegesInsert,
|
||||||
TAdditionalPrivilegesUpdate
|
TAdditionalPrivilegesUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.ExternalMigrationConfig]: KnexOriginal.CompositeTableType<
|
[TableName.VaultExternalMigrationConfig]: KnexOriginal.CompositeTableType<
|
||||||
TExternalMigrationConfigs,
|
TVaultExternalMigrationConfigs,
|
||||||
TExternalMigrationConfigsInsert,
|
TVaultExternalMigrationConfigsInsert,
|
||||||
TExternalMigrationConfigsUpdate
|
TVaultExternalMigrationConfigsUpdate
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,26 @@ import { TableName } from "../schemas";
|
|||||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
if (!(await knex.schema.hasTable(TableName.ExternalMigrationConfig))) {
|
if (!(await knex.schema.hasTable(TableName.VaultExternalMigrationConfig))) {
|
||||||
await knex.schema.createTable(TableName.ExternalMigrationConfig, (t) => {
|
await knex.schema.createTable(TableName.VaultExternalMigrationConfig, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.uuid("orgId").notNullable();
|
t.uuid("orgId").notNullable();
|
||||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
t.string("platform").notNullable();
|
|
||||||
|
t.string("namespace").notNullable();
|
||||||
|
|
||||||
t.uuid("connectionId");
|
t.uuid("connectionId");
|
||||||
t.foreign("connectionId").references("id").inTable(TableName.AppConnection);
|
t.foreign("connectionId").references("id").inTable(TableName.AppConnection);
|
||||||
|
|
||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
t.unique(["orgId", "platform"]);
|
t.unique(["orgId", "namespace"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
await createOnUpdateTrigger(knex, TableName.ExternalMigrationConfig);
|
await createOnUpdateTrigger(knex, TableName.VaultExternalMigrationConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
await knex.schema.dropTableIfExists(TableName.ExternalMigrationConfig);
|
await knex.schema.dropTableIfExists(TableName.VaultExternalMigrationConfig);
|
||||||
await dropOnUpdateTrigger(knex, TableName.ExternalMigrationConfig);
|
await dropOnUpdateTrigger(knex, TableName.VaultExternalMigrationConfig);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
// 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 ExternalMigrationConfigsSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
orgId: z.string().uuid(),
|
|
||||||
platform: z.string(),
|
|
||||||
connectionId: z.string().uuid().nullable().optional(),
|
|
||||||
createdAt: z.date(),
|
|
||||||
updatedAt: z.date()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TExternalMigrationConfigs = z.infer<typeof ExternalMigrationConfigsSchema>;
|
|
||||||
export type TExternalMigrationConfigsInsert = Omit<z.input<typeof ExternalMigrationConfigsSchema>, TImmutableDBKeys>;
|
|
||||||
export type TExternalMigrationConfigsUpdate = Partial<
|
|
||||||
Omit<z.input<typeof ExternalMigrationConfigsSchema>, TImmutableDBKeys>
|
|
||||||
>;
|
|
||||||
@@ -25,7 +25,6 @@ export * from "./dynamic-secrets";
|
|||||||
export * from "./external-certificate-authorities";
|
export * from "./external-certificate-authorities";
|
||||||
export * from "./external-group-org-role-mappings";
|
export * from "./external-group-org-role-mappings";
|
||||||
export * from "./external-kms";
|
export * from "./external-kms";
|
||||||
export * from "./external-migration-configs";
|
|
||||||
export * from "./folder-checkpoint-resources";
|
export * from "./folder-checkpoint-resources";
|
||||||
export * from "./folder-checkpoints";
|
export * from "./folder-checkpoints";
|
||||||
export * from "./folder-commit-changes";
|
export * from "./folder-commit-changes";
|
||||||
@@ -177,5 +176,6 @@ export * from "./user-aliases";
|
|||||||
export * from "./user-encryption-keys";
|
export * from "./user-encryption-keys";
|
||||||
export * from "./user-group-membership";
|
export * from "./user-group-membership";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
|
export * from "./vault-external-migration-configs";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
export * from "./workflow-integrations";
|
export * from "./workflow-integrations";
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export enum TableName {
|
|||||||
PamAccount = "pam_accounts",
|
PamAccount = "pam_accounts",
|
||||||
PamSession = "pam_sessions",
|
PamSession = "pam_sessions",
|
||||||
|
|
||||||
ExternalMigrationConfig = "external_migration_configs"
|
VaultExternalMigrationConfig = "vault_external_migration_configs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
|
||||||
|
|||||||
@@ -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 { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const VaultExternalMigrationConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
namespace: z.string(),
|
||||||
|
connectionId: z.string().uuid().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TVaultExternalMigrationConfigs = z.infer<typeof VaultExternalMigrationConfigsSchema>;
|
||||||
|
export type TVaultExternalMigrationConfigsInsert = Omit<
|
||||||
|
z.input<typeof VaultExternalMigrationConfigsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TVaultExternalMigrationConfigsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof VaultExternalMigrationConfigsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -174,9 +174,9 @@ import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
|
|||||||
import { convertorServiceFactory } from "@app/services/convertor/convertor-service";
|
import { convertorServiceFactory } from "@app/services/convertor/convertor-service";
|
||||||
import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
||||||
import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
|
import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
|
||||||
import { externalMigrationConfigDALFactory } from "@app/services/external-migration/external-migration-config-dal";
|
|
||||||
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
|
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
|
||||||
import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
||||||
|
import { vaultExternalMigrationConfigDALFactory } from "@app/services/external-migration/vault-external-migration-config-dal";
|
||||||
import { folderCheckpointDALFactory } from "@app/services/folder-checkpoint/folder-checkpoint-dal";
|
import { folderCheckpointDALFactory } from "@app/services/folder-checkpoint/folder-checkpoint-dal";
|
||||||
import { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal";
|
import { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal";
|
||||||
import { folderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal";
|
import { folderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal";
|
||||||
@@ -534,7 +534,7 @@ export const registerRoutes = async (
|
|||||||
const membershipRoleDAL = membershipRoleDALFactory(db);
|
const membershipRoleDAL = membershipRoleDALFactory(db);
|
||||||
const roleDAL = roleDALFactory(db);
|
const roleDAL = roleDALFactory(db);
|
||||||
|
|
||||||
const externalMigrationConfigDAL = externalMigrationConfigDALFactory(db);
|
const vaultExternalMigrationConfigDAL = vaultExternalMigrationConfigDALFactory(db);
|
||||||
|
|
||||||
const eventBusService = eventBusFactory(server.redis);
|
const eventBusService = eventBusFactory(server.redis);
|
||||||
const sseService = sseServiceFactory(eventBusService, server.redis);
|
const sseService = sseServiceFactory(eventBusService, server.redis);
|
||||||
@@ -2199,7 +2199,7 @@ export const registerRoutes = async (
|
|||||||
gatewayService,
|
gatewayService,
|
||||||
kmsService,
|
kmsService,
|
||||||
appConnectionService,
|
appConnectionService,
|
||||||
externalMigrationConfigDAL,
|
vaultExternalMigrationConfigDAL,
|
||||||
secretService,
|
secretService,
|
||||||
auditLogService
|
auditLogService
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -117,33 +117,64 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/config",
|
url: "/vault/configs",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
|
||||||
platform: z.nativeEnum(ExternalMigrationProviders)
|
|
||||||
}),
|
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
config: z
|
configs: z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
orgId: z.string(),
|
orgId: z.string(),
|
||||||
platform: z.string(),
|
namespace: z.string(),
|
||||||
connectionId: z.string().nullable().optional(),
|
connectionId: z.string().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date()
|
||||||
})
|
})
|
||||||
.nullable()
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const config = await server.services.migration.getExternalMigrationConfig({
|
const configs = await server.services.migration.getVaultExternalMigrationConfigs({
|
||||||
platform: req.query.platform,
|
actor: req.permission
|
||||||
|
});
|
||||||
|
|
||||||
|
return { configs };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/vault/configs",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
connectionId: z.string(),
|
||||||
|
namespace: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
config: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
orgId: z.string(),
|
||||||
|
namespace: z.string(),
|
||||||
|
connectionId: z.string().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const config = await server.services.migration.createVaultExternalMigration({
|
||||||
|
...req.body,
|
||||||
actor: req.permission
|
actor: req.permission
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,21 +184,24 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
url: "/config",
|
url: "/vault/configs/:id",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
connectionId: z.string().nullable(),
|
connectionId: z.string(),
|
||||||
platform: z.nativeEnum(ExternalMigrationProviders)
|
namespace: z.string()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
config: z.object({
|
config: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
orgId: z.string(),
|
orgId: z.string(),
|
||||||
platform: z.string(),
|
namespace: z.string(),
|
||||||
connectionId: z.string().nullable().optional(),
|
connectionId: z.string().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date()
|
||||||
@@ -175,12 +209,48 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const config = await server.services.migration.configureExternalMigration({
|
const config = await server.services.migration.updateVaultExternalMigration({
|
||||||
|
id: req.params.id,
|
||||||
...req.body,
|
...req.body,
|
||||||
actor: req.permission
|
actor: req.permission
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return { config };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/vault/configs/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
config: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
orgId: z.string(),
|
||||||
|
namespace: z.string(),
|
||||||
|
connectionId: z.string().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const config = await server.services.migration.deleteVaultExternalMigration({
|
||||||
|
id: req.params.id,
|
||||||
|
actor: req.permission
|
||||||
|
});
|
||||||
|
|
||||||
return { config };
|
return { config };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -243,7 +313,7 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
namespace: z.string().optional()
|
namespace: z.string()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
getHCVaultSecretsForPath,
|
getHCVaultSecretsForPath,
|
||||||
HCVaultAuthType,
|
HCVaultAuthType,
|
||||||
listHCVaultMounts,
|
listHCVaultMounts,
|
||||||
listHCVaultNamespaces,
|
|
||||||
listHCVaultPolicies,
|
listHCVaultPolicies,
|
||||||
listHCVaultSecretPaths,
|
listHCVaultSecretPaths,
|
||||||
THCVaultConnection
|
THCVaultConnection
|
||||||
@@ -29,7 +28,6 @@ import { TKmsServiceFactory } from "../kms/kms-service";
|
|||||||
import { TSecretServiceFactory } from "../secret/secret-service";
|
import { TSecretServiceFactory } from "../secret/secret-service";
|
||||||
import { SecretProtectionType } from "../secret/secret-types";
|
import { SecretProtectionType } from "../secret/secret-types";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TExternalMigrationConfigDALFactory } from "./external-migration-config-dal";
|
|
||||||
import {
|
import {
|
||||||
decryptEnvKeyDataFn,
|
decryptEnvKeyDataFn,
|
||||||
importVaultDataFn,
|
importVaultDataFn,
|
||||||
@@ -40,12 +38,15 @@ import { TExternalMigrationQueueFactory } from "./external-migration-queue";
|
|||||||
import {
|
import {
|
||||||
ExternalMigrationProviders,
|
ExternalMigrationProviders,
|
||||||
ExternalPlatforms,
|
ExternalPlatforms,
|
||||||
TConfigureExternalMigrationDTO,
|
TCreateVaultExternalMigrationDTO,
|
||||||
|
TDeleteVaultExternalMigrationDTO,
|
||||||
THasCustomVaultMigrationDTO,
|
THasCustomVaultMigrationDTO,
|
||||||
TImportEnvKeyDataDTO,
|
TImportEnvKeyDataDTO,
|
||||||
TImportVaultDataDTO,
|
TImportVaultDataDTO,
|
||||||
|
TUpdateVaultExternalMigrationDTO,
|
||||||
VaultImportStatus
|
VaultImportStatus
|
||||||
} from "./external-migration-types";
|
} from "./external-migration-types";
|
||||||
|
import { TVaultExternalMigrationConfigDALFactory } from "./vault-external-migration-config-dal";
|
||||||
|
|
||||||
type TExternalMigrationServiceFactoryDep = {
|
type TExternalMigrationServiceFactoryDep = {
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
@@ -53,7 +54,10 @@ type TExternalMigrationServiceFactoryDep = {
|
|||||||
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
externalMigrationQueue: TExternalMigrationQueueFactory;
|
externalMigrationQueue: TExternalMigrationQueueFactory;
|
||||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||||
externalMigrationConfigDAL: Pick<TExternalMigrationConfigDALFactory, "create" | "upsert" | "findOne" | "transaction">;
|
vaultExternalMigrationConfigDAL: Pick<
|
||||||
|
TVaultExternalMigrationConfigDALFactory,
|
||||||
|
"create" | "findOne" | "transaction" | "find" | "updateById" | "deleteById" | "findById"
|
||||||
|
>;
|
||||||
userDAL: Pick<TUserDALFactory, "findById">;
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
@@ -69,7 +73,7 @@ export const externalMigrationServiceFactory = ({
|
|||||||
secretService,
|
secretService,
|
||||||
auditLogService,
|
auditLogService,
|
||||||
appConnectionService,
|
appConnectionService,
|
||||||
externalMigrationConfigDAL,
|
vaultExternalMigrationConfigDAL,
|
||||||
kmsService
|
kmsService
|
||||||
}: TExternalMigrationServiceFactoryDep) => {
|
}: TExternalMigrationServiceFactoryDep) => {
|
||||||
const importEnvKeyData = async ({
|
const importEnvKeyData = async ({
|
||||||
@@ -208,7 +212,35 @@ export const externalMigrationServiceFactory = ({
|
|||||||
return actorOrgId in vaultMigrationTransformMappings;
|
return actorOrgId in vaultMigrationTransformMappings;
|
||||||
};
|
};
|
||||||
|
|
||||||
const configureExternalMigration = async ({ platform, connectionId, actor }: TConfigureExternalMigrationDTO) => {
|
const validateVaultExternalMigrationConnection = async ({
|
||||||
|
connection,
|
||||||
|
namespace
|
||||||
|
}: {
|
||||||
|
connection: THCVaultConnection;
|
||||||
|
namespace: string;
|
||||||
|
}) => {
|
||||||
|
// Allow root namespace access when no namespace is configured on the connection
|
||||||
|
const isRootAccess = namespace === "root" || namespace === "/";
|
||||||
|
const hasNoNamespace = connection.credentials.namespace === undefined;
|
||||||
|
|
||||||
|
if (hasNoNamespace && isRootAccess) {
|
||||||
|
// Skip validation for root access with no configured namespace
|
||||||
|
} else if (connection.credentials.namespace !== namespace) {
|
||||||
|
throw new BadRequestError({ message: "Namespace value does not match the namespace of the connection" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await listHCVaultPolicies(namespace, connection, gatewayService);
|
||||||
|
await listHCVaultSecretPaths(namespace, connection, gatewayService);
|
||||||
|
await listHCVaultMounts(connection, gatewayService);
|
||||||
|
} catch (error) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to establish namespace confiugration. ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createVaultExternalMigration = async ({ namespace, connectionId, actor }: TCreateVaultExternalMigrationDTO) => {
|
||||||
const { hasRole } = await permissionService.getOrgPermission(
|
const { hasRole } = await permissionService.getOrgPermission(
|
||||||
actor.type,
|
actor.type,
|
||||||
actor.id,
|
actor.id,
|
||||||
@@ -218,19 +250,22 @@ export const externalMigrationServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!hasRole(OrgMembershipRole.Admin)) {
|
if (!hasRole(OrgMembershipRole.Admin)) {
|
||||||
throw new ForbiddenRequestError({ message: "Only admins can configure external migration" });
|
throw new ForbiddenRequestError({ message: "Only admins can configure vault external migration" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionId) {
|
const connection = await appConnectionService.connectAppConnectionById<THCVaultConnection>(
|
||||||
if (platform === ExternalMigrationProviders.Vault) {
|
AppConnection.HCVault,
|
||||||
await appConnectionService.connectAppConnectionById(AppConnection.HCVault, connectionId, actor);
|
connectionId,
|
||||||
} else {
|
actor
|
||||||
throw new BadRequestError({ message: "Invalid platform" });
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await externalMigrationConfigDAL.upsert({
|
await validateVaultExternalMigrationConnection({
|
||||||
platform,
|
connection,
|
||||||
|
namespace
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await vaultExternalMigrationConfigDAL.create({
|
||||||
|
namespace,
|
||||||
connectionId,
|
connectionId,
|
||||||
orgId: actor.orgId
|
orgId: actor.orgId
|
||||||
});
|
});
|
||||||
@@ -238,7 +273,12 @@ export const externalMigrationServiceFactory = ({
|
|||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getExternalMigrationConfig = async ({ platform, actor }: { platform: string; actor: OrgServiceActor }) => {
|
const updateVaultExternalMigration = async ({
|
||||||
|
id,
|
||||||
|
namespace,
|
||||||
|
connectionId,
|
||||||
|
actor
|
||||||
|
}: TUpdateVaultExternalMigrationDTO) => {
|
||||||
const { hasRole } = await permissionService.getOrgPermission(
|
const { hasRole } = await permissionService.getOrgPermission(
|
||||||
actor.type,
|
actor.type,
|
||||||
actor.id,
|
actor.id,
|
||||||
@@ -248,19 +288,48 @@ export const externalMigrationServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!hasRole(OrgMembershipRole.Admin)) {
|
if (!hasRole(OrgMembershipRole.Admin)) {
|
||||||
throw new ForbiddenRequestError({ message: "Only admins can view external migration config" });
|
throw new ForbiddenRequestError({ message: "Only admins can update vault external migration" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await externalMigrationConfigDAL.findOne({
|
if (connectionId) {
|
||||||
orgId: actor.orgId,
|
const connection = await appConnectionService.connectAppConnectionById<THCVaultConnection>(
|
||||||
platform
|
AppConnection.HCVault,
|
||||||
|
connectionId,
|
||||||
|
actor
|
||||||
|
);
|
||||||
|
|
||||||
|
await validateVaultExternalMigrationConnection({
|
||||||
|
connection,
|
||||||
|
namespace
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await vaultExternalMigrationConfigDAL.updateById(id, {
|
||||||
|
namespace,
|
||||||
|
connectionId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!config) {
|
return config;
|
||||||
throw new NotFoundError({ message: "External migration config not found" });
|
};
|
||||||
|
|
||||||
|
const getVaultExternalMigrationConfigs = async ({ actor }: { actor: OrgServiceActor }) => {
|
||||||
|
const { hasRole } = await permissionService.getOrgPermission(
|
||||||
|
actor.type,
|
||||||
|
actor.id,
|
||||||
|
actor.orgId,
|
||||||
|
actor.authMethod,
|
||||||
|
actor.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasRole(OrgMembershipRole.Admin)) {
|
||||||
|
throw new ForbiddenRequestError({ message: "Only admins can view vault external migration configs" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
const configs = await vaultExternalMigrationConfigDAL.find({
|
||||||
|
orgId: actor.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return configs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVaultNamespaces = async ({ actor }: { actor: OrgServiceActor }) => {
|
const getVaultNamespaces = async ({ actor }: { actor: OrgServiceActor }) => {
|
||||||
@@ -276,32 +345,18 @@ export const externalMigrationServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "Only admins can view vault namespaces" });
|
throw new ForbiddenRequestError({ message: "Only admins can view vault namespaces" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaultConfig = await externalMigrationConfigDAL.findOne({
|
// Get all configured namespaces for this org
|
||||||
orgId: actor.orgId,
|
const vaultConfigs = await vaultExternalMigrationConfigDAL.find({
|
||||||
platform: ExternalMigrationProviders.Vault
|
orgId: actor.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!vaultConfig) {
|
// Return the configured namespaces as an array of objects with id and name
|
||||||
throw new BadRequestError({ message: "Vault migration config not found" });
|
// where both id and name are the namespace path
|
||||||
}
|
const namespaces = vaultConfigs.map((config) => ({
|
||||||
|
id: config.namespace,
|
||||||
|
name: config.namespace
|
||||||
|
}));
|
||||||
|
|
||||||
if (!vaultConfig.connection) {
|
|
||||||
throw new BadRequestError({ message: "Vault migration connection is not configured" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const credentials = await decryptAppConnectionCredentials({
|
|
||||||
orgId: vaultConfig.orgId,
|
|
||||||
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
|
|
||||||
kmsService,
|
|
||||||
projectId: null
|
|
||||||
});
|
|
||||||
|
|
||||||
const connection = {
|
|
||||||
...vaultConfig.connection,
|
|
||||||
credentials
|
|
||||||
} as THCVaultConnection;
|
|
||||||
|
|
||||||
const namespaces = await listHCVaultNamespaces(connection, gatewayService);
|
|
||||||
return namespaces;
|
return namespaces;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -318,17 +373,17 @@ export const externalMigrationServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "Only admins can view vault policies" });
|
throw new ForbiddenRequestError({ message: "Only admins can view vault policies" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaultConfig = await externalMigrationConfigDAL.findOne({
|
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
|
||||||
orgId: actor.orgId,
|
orgId: actor.orgId,
|
||||||
platform: ExternalMigrationProviders.Vault
|
namespace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!vaultConfig) {
|
if (!vaultConfig) {
|
||||||
throw new NotFoundError({ message: "Vault migration config not found" });
|
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vaultConfig.connection) {
|
if (!vaultConfig.connection) {
|
||||||
throw new BadRequestError({ message: "Vault migration connection is not configured" });
|
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await decryptAppConnectionCredentials({
|
const credentials = await decryptAppConnectionCredentials({
|
||||||
@@ -347,7 +402,7 @@ export const externalMigrationServiceFactory = ({
|
|||||||
return policies;
|
return policies;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVaultMounts = async ({ actor, namespace }: { actor: OrgServiceActor; namespace?: string }) => {
|
const getVaultMounts = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
|
||||||
const { hasRole } = await permissionService.getOrgPermission(
|
const { hasRole } = await permissionService.getOrgPermission(
|
||||||
actor.type,
|
actor.type,
|
||||||
actor.id,
|
actor.id,
|
||||||
@@ -360,17 +415,17 @@ export const externalMigrationServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "Only admins can view vault mounts" });
|
throw new ForbiddenRequestError({ message: "Only admins can view vault mounts" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaultConfig = await externalMigrationConfigDAL.findOne({
|
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
|
||||||
orgId: actor.orgId,
|
orgId: actor.orgId,
|
||||||
platform: ExternalMigrationProviders.Vault
|
namespace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!vaultConfig) {
|
if (!vaultConfig) {
|
||||||
throw new NotFoundError({ message: "Vault migration config not found" });
|
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vaultConfig.connection) {
|
if (!vaultConfig.connection) {
|
||||||
throw new BadRequestError({ message: "Vault migration connection is not configured" });
|
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await decryptAppConnectionCredentials({
|
const credentials = await decryptAppConnectionCredentials({
|
||||||
@@ -402,17 +457,17 @@ export const externalMigrationServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "Only admins can view vault secret paths" });
|
throw new ForbiddenRequestError({ message: "Only admins can view vault secret paths" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaultConfig = await externalMigrationConfigDAL.findOne({
|
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
|
||||||
orgId: actor.orgId,
|
orgId: actor.orgId,
|
||||||
platform: ExternalMigrationProviders.Vault
|
namespace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!vaultConfig) {
|
if (!vaultConfig) {
|
||||||
throw new NotFoundError({ message: "Vault migration config not found" });
|
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vaultConfig.connection) {
|
if (!vaultConfig.connection) {
|
||||||
throw new BadRequestError({ message: "Vault migration connection is not configured" });
|
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await decryptAppConnectionCredentials({
|
const credentials = await decryptAppConnectionCredentials({
|
||||||
@@ -461,17 +516,17 @@ export const externalMigrationServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "Only admins can import vault secrets" });
|
throw new ForbiddenRequestError({ message: "Only admins can import vault secrets" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaultConfig = await externalMigrationConfigDAL.findOne({
|
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
|
||||||
orgId: actor.orgId,
|
orgId: actor.orgId,
|
||||||
platform: ExternalMigrationProviders.Vault
|
namespace: vaultNamespace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!vaultConfig) {
|
if (!vaultConfig) {
|
||||||
throw new NotFoundError({ message: "Vault migration config not found" });
|
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vaultConfig.connection) {
|
if (!vaultConfig.connection) {
|
||||||
throw new BadRequestError({ message: "Vault migration connection is not configured" });
|
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await decryptAppConnectionCredentials({
|
const credentials = await decryptAppConnectionCredentials({
|
||||||
@@ -534,6 +589,34 @@ export const externalMigrationServiceFactory = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteVaultExternalMigration = async ({ id, actor }: TDeleteVaultExternalMigrationDTO) => {
|
||||||
|
const { hasRole } = await permissionService.getOrgPermission(
|
||||||
|
actor.type,
|
||||||
|
actor.id,
|
||||||
|
actor.orgId,
|
||||||
|
actor.authMethod,
|
||||||
|
actor.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasRole(OrgMembershipRole.Admin)) {
|
||||||
|
throw new ForbiddenRequestError({ message: "Only admins can delete vault external migration configs" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await vaultExternalMigrationConfigDAL.findById(id);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
throw new NotFoundError({ message: "Vault migration config not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.orgId !== actor.orgId) {
|
||||||
|
throw new ForbiddenRequestError({ message: "Config does not belong to this organization" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedConfig = await vaultExternalMigrationConfigDAL.deleteById(id);
|
||||||
|
|
||||||
|
return deletedConfig;
|
||||||
|
};
|
||||||
|
|
||||||
const getVaultKubernetesAuthRoles = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
|
const getVaultKubernetesAuthRoles = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
|
||||||
const { hasRole } = await permissionService.getOrgPermission(
|
const { hasRole } = await permissionService.getOrgPermission(
|
||||||
actor.type,
|
actor.type,
|
||||||
@@ -547,17 +630,17 @@ export const externalMigrationServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "Only admins can view vault Kubernetes auth roles" });
|
throw new ForbiddenRequestError({ message: "Only admins can view vault Kubernetes auth roles" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaultConfig = await externalMigrationConfigDAL.findOne({
|
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
|
||||||
orgId: actor.orgId,
|
orgId: actor.orgId,
|
||||||
platform: ExternalMigrationProviders.Vault
|
namespace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!vaultConfig) {
|
if (!vaultConfig) {
|
||||||
throw new NotFoundError({ message: "Vault migration config not found" });
|
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vaultConfig.connection) {
|
if (!vaultConfig.connection) {
|
||||||
throw new BadRequestError({ message: "Vault migration connection is not configured" });
|
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await decryptAppConnectionCredentials({
|
const credentials = await decryptAppConnectionCredentials({
|
||||||
@@ -590,8 +673,10 @@ export const externalMigrationServiceFactory = ({
|
|||||||
importEnvKeyData,
|
importEnvKeyData,
|
||||||
importVaultData,
|
importVaultData,
|
||||||
hasCustomVaultMigration,
|
hasCustomVaultMigration,
|
||||||
configureExternalMigration,
|
createVaultExternalMigration,
|
||||||
getExternalMigrationConfig,
|
getVaultExternalMigrationConfigs,
|
||||||
|
updateVaultExternalMigration,
|
||||||
|
deleteVaultExternalMigration,
|
||||||
getVaultNamespaces,
|
getVaultNamespaces,
|
||||||
getVaultPolicies,
|
getVaultPolicies,
|
||||||
getVaultMounts,
|
getVaultMounts,
|
||||||
|
|||||||
@@ -127,8 +127,20 @@ export enum VaultImportStatus {
|
|||||||
ApprovalRequired = "approval_required"
|
ApprovalRequired = "approval_required"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TConfigureExternalMigrationDTO = {
|
export type TCreateVaultExternalMigrationDTO = {
|
||||||
platform: ExternalMigrationProviders;
|
namespace: string;
|
||||||
|
connectionId: string;
|
||||||
|
actor: OrgServiceActor;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdateVaultExternalMigrationDTO = {
|
||||||
|
id: string;
|
||||||
|
namespace: string;
|
||||||
connectionId: string | null;
|
connectionId: string | null;
|
||||||
actor: OrgServiceActor;
|
actor: OrgServiceActor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TDeleteVaultExternalMigrationDTO = {
|
||||||
|
id: string;
|
||||||
|
actor: OrgServiceActor;
|
||||||
|
};
|
||||||
|
|||||||
+10
-23
@@ -1,39 +1,26 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TExternalMigrationConfigsInsert } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
|
import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TExternalMigrationConfigDALFactory = ReturnType<typeof externalMigrationConfigDALFactory>;
|
export type TVaultExternalMigrationConfigDALFactory = ReturnType<typeof vaultExternalMigrationConfigDALFactory>;
|
||||||
|
|
||||||
export const externalMigrationConfigDALFactory = (db: TDbClient) => {
|
export const vaultExternalMigrationConfigDALFactory = (db: TDbClient) => {
|
||||||
const orm = ormify(db, TableName.ExternalMigrationConfig);
|
const orm = ormify(db, TableName.VaultExternalMigrationConfig);
|
||||||
|
|
||||||
const upsert = async (data: TExternalMigrationConfigsInsert, tx?: Knex) => {
|
const findOne = async (filter: { orgId: string; namespace: string }, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const [doc] = await (tx || db)(TableName.ExternalMigrationConfig)
|
const result = await (tx || db?.replicaNode?.() || db)(TableName.VaultExternalMigrationConfig)
|
||||||
.insert(data)
|
|
||||||
.onConflict(["orgId", "platform"])
|
|
||||||
.merge()
|
|
||||||
.returning("*");
|
|
||||||
return doc;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "UpsertExternalMigrationConfig" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const findOne = async (filter: { orgId: string; platform: string }, tx?: Knex) => {
|
|
||||||
try {
|
|
||||||
const result = await (tx || db?.replicaNode?.() || db)(TableName.ExternalMigrationConfig)
|
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.AppConnection,
|
TableName.AppConnection,
|
||||||
`${TableName.AppConnection}.id`,
|
`${TableName.AppConnection}.id`,
|
||||||
`${TableName.ExternalMigrationConfig}.connectionId`
|
`${TableName.VaultExternalMigrationConfig}.connectionId`
|
||||||
)
|
)
|
||||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||||
.where(buildFindFilter(prependTableNameToFindFilter(TableName.ExternalMigrationConfig, filter)))
|
.where(buildFindFilter(prependTableNameToFindFilter(TableName.VaultExternalMigrationConfig, filter)))
|
||||||
.select(selectAllTableCols(TableName.ExternalMigrationConfig))
|
.select(selectAllTableCols(TableName.VaultExternalMigrationConfig))
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.AppConnection).as("appConnectionId"),
|
db.ref("id").withSchema(TableName.AppConnection).as("appConnectionId"),
|
||||||
db.ref("name").withSchema(TableName.AppConnection).as("appConnectionName"),
|
db.ref("name").withSchema(TableName.AppConnection).as("appConnectionName"),
|
||||||
@@ -76,5 +63,5 @@ export const externalMigrationConfigDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...orm, upsert, findOne };
|
return { ...orm, findOne };
|
||||||
};
|
};
|
||||||
@@ -6,12 +6,7 @@ import { secretKeys } from "@app/hooks/api/secrets/queries";
|
|||||||
|
|
||||||
import { projectKeys } from "../projects";
|
import { projectKeys } from "../projects";
|
||||||
import { externalMigrationQueryKeys } from "./queries";
|
import { externalMigrationQueryKeys } from "./queries";
|
||||||
import {
|
import { TImportVaultSecretsDTO, TVaultExternalMigrationConfig, VaultImportStatus } from "./types";
|
||||||
ExternalMigrationProviders,
|
|
||||||
TExternalMigrationConfig,
|
|
||||||
TImportVaultSecretsDTO,
|
|
||||||
VaultImportStatus
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
export const useImportEnvKey = () => {
|
export const useImportEnvKey = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -75,29 +70,6 @@ export const useImportVault = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdateExternalMigrationConfig = (platform: ExternalMigrationProviders) => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation<TExternalMigrationConfig, Error, { connectionId: string | null }>({
|
|
||||||
mutationFn: async ({ connectionId }: { connectionId: string | null }) => {
|
|
||||||
const { data } = await apiRequest.put<{ config: TExternalMigrationConfig }>(
|
|
||||||
"/api/v3/external-migration/config",
|
|
||||||
{
|
|
||||||
connectionId,
|
|
||||||
platform
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.config;
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: externalMigrationQueryKeys.config(platform)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useImportVaultSecrets = () => {
|
export const useImportVaultSecrets = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
@@ -121,3 +93,73 @@ export const useImportVaultSecrets = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCreateVaultExternalMigrationConfig = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<
|
||||||
|
TVaultExternalMigrationConfig,
|
||||||
|
Error,
|
||||||
|
{ connectionId: string; namespace: string }
|
||||||
|
>({
|
||||||
|
mutationFn: async ({ connectionId, namespace }) => {
|
||||||
|
const { data } = await apiRequest.post<{ config: TVaultExternalMigrationConfig }>(
|
||||||
|
"/api/v3/external-migration/vault/configs",
|
||||||
|
{
|
||||||
|
connectionId,
|
||||||
|
namespace
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data.config;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: externalMigrationQueryKeys.vaultConfigs()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateVaultExternalMigrationConfig = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<
|
||||||
|
TVaultExternalMigrationConfig,
|
||||||
|
Error,
|
||||||
|
{ id: string; connectionId: string; namespace: string }
|
||||||
|
>({
|
||||||
|
mutationFn: async ({ id, connectionId, namespace }) => {
|
||||||
|
const { data } = await apiRequest.put<{ config: TVaultExternalMigrationConfig }>(
|
||||||
|
`/api/v3/external-migration/vault/configs/${id}`,
|
||||||
|
{
|
||||||
|
connectionId,
|
||||||
|
namespace
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data.config;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: externalMigrationQueryKeys.vaultConfigs()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteVaultExternalMigrationConfig = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<TVaultExternalMigrationConfig, Error, { id: string }>({
|
||||||
|
mutationFn: async ({ id }) => {
|
||||||
|
const { data } = await apiRequest.delete<{ config: TVaultExternalMigrationConfig }>(
|
||||||
|
`/api/v3/external-migration/vault/configs/${id}`
|
||||||
|
);
|
||||||
|
return data.config;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: externalMigrationQueryKeys.vaultConfigs()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { apiRequest } from "@app/config/request";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ExternalMigrationProviders,
|
ExternalMigrationProviders,
|
||||||
TExternalMigrationConfig,
|
TVaultExternalMigrationConfig,
|
||||||
VaultKubernetesAuthRole
|
VaultKubernetesAuthRole
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ export const externalMigrationQueryKeys = {
|
|||||||
"custom-migration-available",
|
"custom-migration-available",
|
||||||
provider
|
provider
|
||||||
],
|
],
|
||||||
config: (platform: string) => ["external-migration-config", { platform }],
|
vaultConfigs: () => ["vault-external-migration-configs"],
|
||||||
vaultNamespaces: () => ["vault-namespaces"],
|
vaultNamespaces: () => ["vault-namespaces"],
|
||||||
vaultPolicies: (namespace?: string) => ["vault-policies", namespace],
|
vaultPolicies: (namespace?: string) => ["vault-policies", namespace],
|
||||||
vaultMounts: (namespace?: string) => ["vault-mounts", namespace],
|
vaultMounts: (namespace?: string) => ["vault-mounts", namespace],
|
||||||
@@ -31,19 +31,15 @@ export const useHasCustomMigrationAvailable = (provider: ExternalMigrationProvid
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetExternalMigrationConfig = (platform: string) => {
|
export const useGetVaultExternalMigrationConfigs = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: externalMigrationQueryKeys.config(platform),
|
queryKey: externalMigrationQueryKeys.vaultConfigs(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiRequest.get<{ config: TExternalMigrationConfig | null }>(
|
const { data } = await apiRequest.get<{ configs: TVaultExternalMigrationConfig[] }>(
|
||||||
"/api/v3/external-migration/config",
|
"/api/v3/external-migration/vault/configs"
|
||||||
{
|
|
||||||
params: { platform }
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
return data.config;
|
return data.configs;
|
||||||
},
|
}
|
||||||
enabled: Boolean(platform)
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ export enum VaultImportStatus {
|
|||||||
ApprovalRequired = "approval_required"
|
ApprovalRequired = "approval_required"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TExternalMigrationConfig = {
|
export type TVaultExternalMigrationConfig = {
|
||||||
id: string;
|
id: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
platform: string;
|
namespace: string;
|
||||||
connectionId: string | null;
|
connectionId: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|||||||
+4
-7
@@ -37,11 +37,8 @@ import {
|
|||||||
IdentityKubernetesAuthTokenReviewMode,
|
IdentityKubernetesAuthTokenReviewMode,
|
||||||
IdentityTrustedIp
|
IdentityTrustedIp
|
||||||
} from "@app/hooks/api/identities/types";
|
} from "@app/hooks/api/identities/types";
|
||||||
import { useGetExternalMigrationConfig } from "@app/hooks/api/migration/queries";
|
import { useGetVaultExternalMigrationConfigs } from "@app/hooks/api/migration/queries";
|
||||||
import {
|
import { VaultKubernetesAuthRole } from "@app/hooks/api/migration/types";
|
||||||
ExternalMigrationProviders,
|
|
||||||
VaultKubernetesAuthRole
|
|
||||||
} from "@app/hooks/api/migration/types";
|
|
||||||
import { usePopUp, UsePopUpState } from "@app/hooks/usePopUp";
|
import { usePopUp, UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { IdentityFormTab } from "./types";
|
import { IdentityFormTab } from "./types";
|
||||||
@@ -130,8 +127,8 @@ export const IdentityKubernetesAuthForm = ({
|
|||||||
const { popUp, handlePopUpToggle: handleImportPopUpToggle } = usePopUp([
|
const { popUp, handlePopUpToggle: handleImportPopUpToggle } = usePopUp([
|
||||||
"importFromVault"
|
"importFromVault"
|
||||||
] as const);
|
] as const);
|
||||||
const { data: vaultConfig } = useGetExternalMigrationConfig(ExternalMigrationProviders.Vault);
|
const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs();
|
||||||
const hasVaultConnection = Boolean(vaultConfig?.connectionId);
|
const hasVaultConnection = vaultConfigs.some((config) => config.connectionId);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
|||||||
+155
-67
@@ -1,100 +1,188 @@
|
|||||||
import { useMemo } from "react";
|
import { useState } from "react";
|
||||||
|
import { faEdit, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
import {
|
||||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
EmptyState,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
import { useListAppConnections } from "@app/hooks/api/appConnections/queries";
|
import { useListAppConnections } from "@app/hooks/api/appConnections/queries";
|
||||||
import {
|
import {
|
||||||
useGetExternalMigrationConfig,
|
useDeleteVaultExternalMigrationConfig,
|
||||||
useUpdateExternalMigrationConfig
|
useGetVaultExternalMigrationConfigs
|
||||||
} from "@app/hooks/api/migration";
|
} from "@app/hooks/api/migration";
|
||||||
import { ExternalMigrationProviders } from "@app/hooks/api/migration/types";
|
import { TVaultExternalMigrationConfig } from "@app/hooks/api/migration/types";
|
||||||
|
|
||||||
|
import { VaultNamespaceConfigModal } from "./VaultNamespaceConfigModal";
|
||||||
|
|
||||||
export const VaultConnectionSection = () => {
|
export const VaultConnectionSection = () => {
|
||||||
const { data: appConnections = [], isPending: isLoadingConnections } = useListAppConnections();
|
const [selectedConfig, setSelectedConfig] = useState<TVaultExternalMigrationConfig | null>(null);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [configToDelete, setConfigToDelete] = useState<TVaultExternalMigrationConfig | null>(null);
|
||||||
|
|
||||||
const vaultConnections = useMemo(
|
const { data: configs = [], isPending: isLoadingConfigs } = useGetVaultExternalMigrationConfigs();
|
||||||
() => appConnections.filter((conn) => conn.app === AppConnection.HCVault),
|
const { data: appConnections = [] } = useListAppConnections();
|
||||||
[appConnections]
|
const { mutateAsync: deleteConfig } = useDeleteVaultExternalMigrationConfig();
|
||||||
);
|
|
||||||
|
|
||||||
const { data: currentConfig, isPending: isLoadingConfig } = useGetExternalMigrationConfig(
|
const handleEdit = (config: TVaultExternalMigrationConfig) => {
|
||||||
ExternalMigrationProviders.Vault
|
setSelectedConfig(config);
|
||||||
);
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const { mutateAsync: updateConfig, isPending: isUpdating } = useUpdateExternalMigrationConfig(
|
const handleAdd = () => {
|
||||||
ExternalMigrationProviders.Vault
|
setSelectedConfig(null);
|
||||||
);
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteClick = (config: TVaultExternalMigrationConfig) => {
|
||||||
|
setConfigToDelete(config);
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteConfirm = async () => {
|
||||||
|
if (!configToDelete) return;
|
||||||
|
|
||||||
const handleConnectionChange = async (
|
|
||||||
selectedConnection: { id: string; name: string } | null
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
await updateConfig({
|
await deleteConfig({ id: configToDelete.id });
|
||||||
connectionId: selectedConnection?.id || null
|
|
||||||
});
|
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Vault connection updated successfully"
|
text: "Namespace configuration deleted successfully"
|
||||||
});
|
});
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
setConfigToDelete(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update vault connection:", error);
|
console.error("Failed to delete namespace config:", error);
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "error",
|
type: "error",
|
||||||
text: "Failed to update vault connection"
|
text: "Failed to delete namespace configuration"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedConnection = useMemo(() => {
|
const getConnectionName = (connectionId: string | null) => {
|
||||||
if (!currentConfig?.connectionId) return null;
|
if (!connectionId) return "None";
|
||||||
return vaultConnections.find((conn) => conn.id === currentConfig.connectionId) || null;
|
const connection = appConnections.find((conn) => conn.id === connectionId);
|
||||||
}, [currentConfig?.connectionId, vaultConnections]);
|
return connection?.name || "Unknown";
|
||||||
|
};
|
||||||
const isLoading = isLoadingConnections || isLoadingConfig;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-4 flex items-center gap-3">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<img
|
<div className="flex items-center gap-3">
|
||||||
src="/images/integrations/Vault.png"
|
<img
|
||||||
alt="HashiCorp Vault logo"
|
src="/images/integrations/Vault.png"
|
||||||
className="bg-bunker-500 h-10 w-10 rounded-md p-2"
|
alt="HashiCorp Vault logo"
|
||||||
/>
|
className="bg-bunker-500 h-10 w-10 rounded-md p-2"
|
||||||
<div>
|
|
||||||
<h3 className="text-mineshaft-100 text-lg font-medium">HashiCorp Vault</h3>
|
|
||||||
<p className="text-sm text-gray-400">
|
|
||||||
Enable in-platform migration tooling for policy imports and secret engine migrations
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-w-md">
|
|
||||||
<FormControl
|
|
||||||
label="HashiCorp Vault Connection"
|
|
||||||
tooltipText="Select an existing App Connection to enable in-platform migration features. Manage connections in the App Connections section."
|
|
||||||
>
|
|
||||||
<FilterableSelect
|
|
||||||
value={selectedConnection}
|
|
||||||
onChange={(newValue) => {
|
|
||||||
handleConnectionChange(newValue as { id: string; name: string } | null);
|
|
||||||
}}
|
|
||||||
isLoading={isLoading}
|
|
||||||
isDisabled={isUpdating}
|
|
||||||
options={vaultConnections}
|
|
||||||
placeholder="Select connection..."
|
|
||||||
getOptionLabel={(option) => option.name}
|
|
||||||
getOptionValue={(option) => option.id}
|
|
||||||
isClearable
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
<div>
|
||||||
|
<h3 className="text-mineshaft-100 text-lg font-medium">HashiCorp Vault</h3>
|
||||||
|
<p className="text-sm text-gray-400">
|
||||||
|
Enable in-platform migration tooling for policy imports and secret engine migrations
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={handleAdd}
|
||||||
|
>
|
||||||
|
Add Namespace
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-mineshaft-400 mt-2 text-xs">
|
<TableContainer>
|
||||||
Select an existing App Connection to enable in-platform migration features. Manage
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Namespace</Th>
|
||||||
|
<Th>Connection</Th>
|
||||||
|
<Th className="w-5" />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isLoadingConfigs && (
|
||||||
|
<TableSkeleton columns={3} innerKey="vault-configs-loading" rows={3} />
|
||||||
|
)}
|
||||||
|
{!isLoadingConfigs && configs.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={3}>
|
||||||
|
<EmptyState title="No namespace configurations" icon={faPlus} className="py-8">
|
||||||
|
<p className="text-mineshaft-400 mb-4 text-sm">
|
||||||
|
Add a namespace configuration to enable in-platform migration features.
|
||||||
|
</p>
|
||||||
|
</EmptyState>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
{!isLoadingConfigs &&
|
||||||
|
configs.map((config) => (
|
||||||
|
<Tr key={config.id} className="group h-10">
|
||||||
|
<Td>{config.namespace}</Td>
|
||||||
|
<Td>{getConnectionName(config.connectionId)}</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex items-center justify-end gap-2 opacity-0 transition-opacity group-hover:opacity-100">
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
colorSchema="secondary"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => handleEdit(config)}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faEdit} />}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
colorSchema="danger"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => handleDeleteClick(config)}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faTrash} />}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<p className="text-mineshaft-400 mt-4 text-xs">
|
||||||
|
Configure namespace-specific connections to enable in-platform migration features. Manage
|
||||||
connections in the App Connections section.
|
connections in the App Connections section.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<VaultNamespaceConfigModal
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setIsModalOpen(open);
|
||||||
|
if (!open) setSelectedConfig(null);
|
||||||
|
}}
|
||||||
|
editConfig={selectedConfig || undefined}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={isDeleteModalOpen}
|
||||||
|
title={`Delete namespace configuration for "${configToDelete?.namespace}"?`}
|
||||||
|
onChange={(open) => {
|
||||||
|
setIsDeleteModalOpen(open);
|
||||||
|
if (!open) setConfigToDelete(null);
|
||||||
|
}}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={handleDeleteConfirm}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FilterableSelect,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { useListAppConnections } from "@app/hooks/api/appConnections/queries";
|
||||||
|
import {
|
||||||
|
useCreateVaultExternalMigrationConfig,
|
||||||
|
useUpdateVaultExternalMigrationConfig
|
||||||
|
} from "@app/hooks/api/migration";
|
||||||
|
import { TVaultExternalMigrationConfig } from "@app/hooks/api/migration/types";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
namespace: z.string().min(1, "Namespace is required"),
|
||||||
|
connectionId: z.string().min(1, "Connection is required")
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
|
editConfig?: TVaultExternalMigrationConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VaultNamespaceConfigModal = ({ isOpen, onOpenChange, editConfig }: Props) => {
|
||||||
|
const isEdit = Boolean(editConfig);
|
||||||
|
|
||||||
|
const { data: appConnections = [], isPending: isLoadingConnections } = useListAppConnections();
|
||||||
|
|
||||||
|
const vaultConnections = useMemo(
|
||||||
|
() => appConnections.filter((conn) => conn.app === AppConnection.HCVault),
|
||||||
|
[appConnections]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutateAsync: createConfig, isPending: isCreating } =
|
||||||
|
useCreateVaultExternalMigrationConfig();
|
||||||
|
const { mutateAsync: updateConfig, isPending: isUpdating } =
|
||||||
|
useUpdateVaultExternalMigrationConfig();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { errors, isSubmitting }
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
namespace: "",
|
||||||
|
connectionId: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset form when editConfig changes or modal opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
reset({
|
||||||
|
namespace: editConfig?.namespace || "",
|
||||||
|
connectionId: editConfig?.connectionId || ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isOpen, editConfig, reset]);
|
||||||
|
|
||||||
|
const onFormSubmit = async (data: FormData) => {
|
||||||
|
try {
|
||||||
|
if (isEdit && editConfig) {
|
||||||
|
await updateConfig({
|
||||||
|
id: editConfig.id,
|
||||||
|
namespace: data.namespace,
|
||||||
|
connectionId: data.connectionId
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Namespace configuration updated successfully"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createConfig({
|
||||||
|
namespace: data.namespace,
|
||||||
|
connectionId: data.connectionId
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Namespace configuration created successfully"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
onOpenChange(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to save namespace config:", error);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: `Failed to ${isEdit ? "update" : "create"} namespace configuration`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
reset();
|
||||||
|
onOpenChange(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={handleClose}>
|
||||||
|
<ModalContent
|
||||||
|
title={isEdit ? "Edit Namespace Configuration" : "Add Namespace Configuration"}
|
||||||
|
subTitle={`Configure a HashiCorp Vault namespace ${isEdit ? "configuration" : "for migration tooling"}`}
|
||||||
|
bodyClassName="overflow-visible"
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="namespace"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Namespace"
|
||||||
|
isError={Boolean(errors.namespace)}
|
||||||
|
errorText={errors.namespace?.message}
|
||||||
|
className="mb-4"
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="e.g., admin, dev, prod" autoComplete="off" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="connectionId"
|
||||||
|
render={({ field }) => {
|
||||||
|
const selectedConnection = vaultConnections.find((conn) => conn.id === field.value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
label="Vault Connection"
|
||||||
|
isError={Boolean(errors.connectionId)}
|
||||||
|
errorText={errors.connectionId?.message}
|
||||||
|
tooltipText="Select a HashiCorp Vault app connection for this namespace"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
value={selectedConnection || null}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
const singleValue = Array.isArray(newValue) ? newValue[0] : newValue;
|
||||||
|
if (singleValue && "id" in singleValue) {
|
||||||
|
field.onChange(singleValue.id);
|
||||||
|
} else {
|
||||||
|
field.onChange("");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
isLoading={isLoadingConnections}
|
||||||
|
options={vaultConnections}
|
||||||
|
placeholder="Select connection..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.id}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-8 flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting || isCreating || isUpdating}
|
||||||
|
isDisabled={isSubmitting || isCreating || isUpdating}
|
||||||
|
>
|
||||||
|
{isEdit ? "Update" : "Create"}
|
||||||
|
</Button>
|
||||||
|
<Button colorSchema="secondary" variant="plain" onClick={handleClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -12,8 +12,7 @@ import {
|
|||||||
import { useOrgPermission } from "@app/context";
|
import { useOrgPermission } from "@app/context";
|
||||||
import { OrgMembershipRole } from "@app/helpers/roles";
|
import { OrgMembershipRole } from "@app/helpers/roles";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useGetExternalMigrationConfig } from "@app/hooks/api/migration";
|
import { useGetVaultExternalMigrationConfigs } from "@app/hooks/api/migration";
|
||||||
import { ExternalMigrationProviders } from "@app/hooks/api/migration/types";
|
|
||||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||||
import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal";
|
import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal";
|
||||||
import { PolicyTemplateModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal";
|
import { PolicyTemplateModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal";
|
||||||
@@ -33,9 +32,8 @@ export const AddPoliciesButton = ({ isDisabled, projectType }: Props) => {
|
|||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const { hasOrgRole } = useOrgPermission();
|
const { hasOrgRole } = useOrgPermission();
|
||||||
const { data: vaultConfig } = useGetExternalMigrationConfig(ExternalMigrationProviders.Vault);
|
const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs();
|
||||||
|
const hasVaultConnection = vaultConfigs.some((config) => config.connectionId);
|
||||||
const hasVaultConnection = Boolean(vaultConfig?.connectionId);
|
|
||||||
const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin);
|
const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin);
|
||||||
const isVaultImportDisabled = isDisabled || !isOrgAdmin;
|
const isVaultImportDisabled = isDisabled || !isOrgAdmin;
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -77,8 +77,11 @@ import {
|
|||||||
fetchDashboardProjectSecretsByKeys
|
fetchDashboardProjectSecretsByKeys
|
||||||
} from "@app/hooks/api/dashboard/queries";
|
} from "@app/hooks/api/dashboard/queries";
|
||||||
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
|
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
|
||||||
import { useGetExternalMigrationConfig, useImportVaultSecrets } from "@app/hooks/api/migration";
|
import {
|
||||||
import { ExternalMigrationProviders, VaultImportStatus } from "@app/hooks/api/migration/types";
|
useGetVaultExternalMigrationConfigs,
|
||||||
|
useImportVaultSecrets
|
||||||
|
} from "@app/hooks/api/migration";
|
||||||
|
import { VaultImportStatus } from "@app/hooks/api/migration/types";
|
||||||
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
|
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
|
||||||
import { PendingAction } from "@app/hooks/api/secretFolders/types";
|
import { PendingAction } from "@app/hooks/api/secretFolders/types";
|
||||||
import { fetchProjectSecrets, secretKeys } from "@app/hooks/api/secrets/queries";
|
import { fetchProjectSecrets, secretKeys } from "@app/hooks/api/secrets/queries";
|
||||||
@@ -198,8 +201,8 @@ export const ActionBar = ({
|
|||||||
const isMultiSelectActive = Boolean(Object.keys(selectedSecrets).length);
|
const isMultiSelectActive = Boolean(Object.keys(selectedSecrets).length);
|
||||||
|
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
const { data: vaultConfig } = useGetExternalMigrationConfig(ExternalMigrationProviders.Vault);
|
const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs();
|
||||||
const hasVaultConnection = Boolean(vaultConfig?.connectionId);
|
const hasVaultConnection = vaultConfigs.some((config) => config.connectionId);
|
||||||
|
|
||||||
const handleFolderCreate = async (folderName: string, description: string | null) => {
|
const handleFolderCreate = async (folderName: string, description: string | null) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user