reduce code reusability

This commit is contained in:
x032205
2026-01-07 20:24:20 -05:00
parent 30918db81a
commit 91e0e2c7e0
2 changed files with 79 additions and 377 deletions

View File

@@ -49,6 +49,23 @@ export const convertVaultValueToString = (value: JsonValue): string => {
// Concurrency limit for HC Vault API requests to avoid rate limiting
const HC_VAULT_CONCURRENCY_LIMIT = 20;
// Helper to check if error is a 404
const isVault404Error = (error: unknown): boolean => {
if (error && typeof error === "object" && "response" in error) {
const axiosError = error as { response?: { status?: number } };
return axiosError.response?.status === 404;
}
return false;
};
// Helper to extract error message from Vault API errors
const getVaultErrorMessage = (error: unknown, fallback: string): string => {
if (error instanceof AxiosError) {
return (error.response?.data as { errors?: string[] })?.errors?.[0] || error.message || fallback;
}
return fallback;
};
/**
* Creates a concurrency limiter that restricts the number of concurrent async operations
* @param limit - Maximum number of concurrent operations
@@ -866,14 +883,7 @@ export const getHCVaultKubernetesRoles = async (
);
roleNames = roleListResponse.data.keys || [];
} catch (error) {
// Vault returns 404 when no roles are configured yet
if (error && typeof error === "object" && "response" in error) {
const axiosError = error as { response?: { status?: number } };
if (axiosError.response?.status === 404) {
return [];
}
}
if (isVault404Error(error)) return [];
throw error;
}
@@ -910,7 +920,6 @@ export const getHCVaultKubernetesRoles = async (
}
});
// 4. Merge the role with the config
return {
...roleResponse.data,
name: roleName,
@@ -920,22 +929,11 @@ export const getHCVaultKubernetesRoles = async (
})
);
const roles = await Promise.all(roleDetailsPromises);
return roles;
return Promise.all(roleDetailsPromises);
} catch (error: unknown) {
logger.error(error, "Unable to list HC Vault Kubernetes secrets engine roles");
if (error instanceof AxiosError) {
const errorMessage =
(error.response?.data as { errors?: string[] })?.errors?.[0] || error.message || "Unknown error";
throw new BadRequestError({
message: `Failed to list Kubernetes secrets engine roles: ${errorMessage}`
});
}
throw new BadRequestError({
message: "Unable to list Kubernetes secrets engine roles from HashiCorp Vault"
message: `Failed to list Kubernetes secrets engine roles: ${getVaultErrorMessage(error, "Unknown error")}`
});
}
};
@@ -970,13 +968,7 @@ export const getHCVaultDatabaseRoles = async (
);
connectionNames = connectionListResponse.data.keys || [];
} catch (error) {
// Vault returns 404 when no connections are configured yet
if (error && typeof error === "object" && "response" in error) {
const axiosError = error as { response?: { status?: number } };
if (axiosError.response?.status === 404) {
return [];
}
}
if (isVault404Error(error)) return [];
throw error;
}
@@ -1023,13 +1015,7 @@ export const getHCVaultDatabaseRoles = async (
);
roleNames = roleListResponse.data.keys || [];
} catch (error) {
// Vault returns 404 when no roles are configured yet
if (error && typeof error === "object" && "response" in error) {
const axiosError = error as { response?: { status?: number } };
if (axiosError.response?.status === 404) {
return [];
}
}
if (isVault404Error(error)) return [];
throw error;
}
@@ -1047,13 +1033,7 @@ export const getHCVaultDatabaseRoles = async (
max_ttl?: number;
creation_statements?: string[];
revocation_statements?: string[];
rollback_statements?: string[];
renew_statements?: string[];
rotation_statements?: string[];
credential_type?: string;
credential_config?: {
password_policy?: string;
};
};
}>(connection, gatewayService, {
url: `${instanceUrl}/v1/${cleanMountPath}/roles/${roleName}`,
@@ -1064,12 +1044,9 @@ export const getHCVaultDatabaseRoles = async (
}
});
// Get the connection config for this role's db_name
const dbConfig = connectionConfigs.get(roleResponse.data.db_name) || {
plugin_name: "",
connection_details: {
connection_url: ""
}
connection_details: { connection_url: "" }
};
return {
@@ -1086,22 +1063,11 @@ export const getHCVaultDatabaseRoles = async (
})
);
const roles = await Promise.all(roleDetailsPromises);
return roles;
return Promise.all(roleDetailsPromises);
} catch (error: unknown) {
logger.error(error, "Unable to list HC Vault database secrets engine roles");
if (error instanceof AxiosError) {
const errorMessage =
(error.response?.data as { errors?: string[] })?.errors?.[0] || error.message || "Unknown error";
throw new BadRequestError({
message: `Failed to list database secrets engine roles: ${errorMessage}`
});
}
throw new BadRequestError({
message: "Unable to list database secrets engine roles from HashiCorp Vault"
message: `Failed to list database secrets engine roles: ${getVaultErrorMessage(error, "Unknown error")}`
});
}
};

View File

@@ -80,6 +80,47 @@ export const externalMigrationServiceFactory = ({
vaultExternalMigrationConfigDAL,
kmsService
}: TExternalMigrationServiceFactoryDep) => {
// Helper to verify admin permissions and get vault connection
const getVaultConnectionForNamespace = async (actor: OrgServiceActor, namespace: string, action: string) => {
const { hasRole } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: actor.type,
actorId: actor.id,
orgId: actor.orgId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: `Only admins can ${action}` });
}
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
orgId: actor.orgId,
namespace
});
if (!vaultConfig) {
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
}
if (!vaultConfig.connection) {
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
}
const credentials = await decryptAppConnectionCredentials({
orgId: vaultConfig.orgId,
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
kmsService,
projectId: null
});
return {
...vaultConfig.connection,
credentials
} as THCVaultConnection;
};
const importEnvKeyData = async ({
decryptionKey,
encryptedJson,
@@ -390,89 +431,13 @@ export const externalMigrationServiceFactory = ({
};
const getVaultPolicies = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault policies" });
}
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
orgId: actor.orgId,
namespace
});
if (!vaultConfig) {
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
}
if (!vaultConfig.connection) {
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
}
const credentials = await decryptAppConnectionCredentials({
orgId: vaultConfig.orgId,
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
kmsService,
projectId: null
});
const connection = {
...vaultConfig.connection,
credentials
} as THCVaultConnection;
const policies = await listHCVaultPolicies(namespace, connection, gatewayService);
return policies;
const connection = await getVaultConnectionForNamespace(actor, namespace, "view vault policies");
return listHCVaultPolicies(namespace, connection, gatewayService);
};
const getVaultMounts = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault mounts" });
}
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
orgId: actor.orgId,
namespace
});
if (!vaultConfig) {
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
}
if (!vaultConfig.connection) {
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
}
const credentials = await decryptAppConnectionCredentials({
orgId: vaultConfig.orgId,
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
kmsService,
projectId: null
});
const connection = {
...vaultConfig.connection,
credentials
} as THCVaultConnection;
const mounts = await listHCVaultMounts(connection, gatewayService, namespace);
return mounts;
const connection = await getVaultConnectionForNamespace(actor, namespace, "view vault mounts");
return listHCVaultMounts(connection, gatewayService, namespace);
};
const getVaultSecretPaths = async ({
@@ -484,47 +449,8 @@ export const externalMigrationServiceFactory = ({
namespace: string;
mountPath: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault secret paths" });
}
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
orgId: actor.orgId,
namespace
});
if (!vaultConfig) {
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
}
if (!vaultConfig.connection) {
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
}
const credentials = await decryptAppConnectionCredentials({
orgId: vaultConfig.orgId,
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
kmsService,
projectId: null
});
const connection = {
...vaultConfig.connection,
credentials
} as THCVaultConnection;
const secretPaths = await listHCVaultSecretPaths(namespace, connection, gatewayService, mountPath);
return secretPaths;
const connection = await getVaultConnectionForNamespace(actor, namespace, "view vault secret paths");
return listHCVaultSecretPaths(namespace, connection, gatewayService, mountPath);
};
const importVaultSecrets = async ({
@@ -544,44 +470,7 @@ export const externalMigrationServiceFactory = ({
vaultSecretPath: string;
auditLogInfo: AuditLogInfo;
}) => {
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can import vault secrets" });
}
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
orgId: actor.orgId,
namespace: vaultNamespace
});
if (!vaultConfig) {
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
}
if (!vaultConfig.connection) {
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
}
const credentials = await decryptAppConnectionCredentials({
orgId: vaultConfig.orgId,
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
kmsService,
projectId: null
});
const connection = {
...vaultConfig.connection,
credentials
} as THCVaultConnection;
const connection = await getVaultConnectionForNamespace(actor, vaultNamespace, "import vault secrets");
const vaultSecrets = await getHCVaultSecretsForPath(vaultNamespace, vaultSecretPath, connection, gatewayService);
try {
@@ -668,47 +557,8 @@ export const externalMigrationServiceFactory = ({
namespace: string;
authType?: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault auth mounts" });
}
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
orgId: actor.orgId,
namespace
});
if (!vaultConfig) {
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
}
if (!vaultConfig.connection) {
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
}
const credentials = await decryptAppConnectionCredentials({
orgId: vaultConfig.orgId,
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
kmsService,
projectId: null
});
const connection = {
...vaultConfig.connection,
credentials
} as THCVaultConnection;
const authMounts = await getHCVaultAuthMounts(namespace, authType as HCVaultAuthType, connection, gatewayService);
return authMounts;
const connection = await getVaultConnectionForNamespace(actor, namespace, "view vault auth mounts");
return getHCVaultAuthMounts(namespace, authType as HCVaultAuthType, connection, gatewayService);
};
const getVaultKubernetesAuthRoles = async ({
@@ -720,48 +570,8 @@ export const externalMigrationServiceFactory = ({
namespace: string;
mountPath: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault Kubernetes auth roles" });
}
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
orgId: actor.orgId,
namespace
});
if (!vaultConfig) {
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
}
if (!vaultConfig.connection) {
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
}
const credentials = await decryptAppConnectionCredentials({
orgId: vaultConfig.orgId,
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
kmsService,
projectId: null
});
const connection = {
...vaultConfig.connection,
credentials
} as THCVaultConnection;
// Get roles for the specified mount path only
const roles = await getHCVaultKubernetesAuthRoles(namespace, mountPath, connection, gatewayService);
return roles;
const connection = await getVaultConnectionForNamespace(actor, namespace, "view vault Kubernetes auth roles");
return getHCVaultKubernetesAuthRoles(namespace, mountPath, connection, gatewayService);
};
const getVaultKubernetesRoles = async ({
@@ -773,44 +583,7 @@ export const externalMigrationServiceFactory = ({
namespace: string;
mountPath: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: actor.type,
actorId: actor.id,
orgId: actor.orgId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can get Kubernetes roles" });
}
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
orgId: actor.orgId,
namespace
});
if (!vaultConfig) {
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
}
if (!vaultConfig.connection) {
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
}
const credentials = await decryptAppConnectionCredentials({
orgId: vaultConfig.orgId,
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
kmsService,
projectId: null
});
const connection = {
...vaultConfig.connection,
credentials
} as THCVaultConnection;
const connection = await getVaultConnectionForNamespace(actor, namespace, "get Kubernetes roles");
return getHCVaultKubernetesRoles(namespace, mountPath, connection, gatewayService);
};
@@ -823,44 +596,7 @@ export const externalMigrationServiceFactory = ({
namespace: string;
mountPath: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: actor.type,
actorId: actor.id,
orgId: actor.orgId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can get database roles" });
}
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
orgId: actor.orgId,
namespace
});
if (!vaultConfig) {
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
}
if (!vaultConfig.connection) {
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
}
const credentials = await decryptAppConnectionCredentials({
orgId: vaultConfig.orgId,
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
kmsService,
projectId: null
});
const connection = {
...vaultConfig.connection,
credentials
} as THCVaultConnection;
const connection = await getVaultConnectionForNamespace(actor, namespace, "get database roles");
return getHCVaultDatabaseRoles(namespace, mountPath, connection, gatewayService);
};