Merge remote-tracking branch 'origin/main' into pki-revamp-v3

This commit is contained in:
Carlos Monastyrski
2025-10-20 14:27:19 -03:00
201 changed files with 7648 additions and 3401 deletions
@@ -40,7 +40,7 @@ describe("Secret Folder Router", async () => {
{ name: "folder1", path: "/" }, // one in root
{ name: "folder1", path: "/level1/level2" }, // then create a deep one creating intermediate ones
{ name: "folder2", path: "/" },
{ name: "folder1", path: "/level1/level2" } // this should not create folder return same thing
{ name: "folder3", path: "/level1/level2" }
])("Create folder $name in $path", async ({ name, path }) => {
const createdFolder = await createFolder({ path, name });
// check for default environments
@@ -57,7 +57,7 @@ describe("Secret Folder Router", async () => {
{
path: "/",
expected: {
folders: [{ name: "folder1" }, { name: "level1" }, { name: "folder2" }],
folders: [{ name: "folder4" }, { name: "level2" }, { name: "folder5" }],
length: 3
}
},
@@ -162,4 +162,25 @@ describe("Secret Folder Router", async () => {
expect(updatedFolderList).toHaveProperty("folders");
expect(updatedFolderList.folders.length).toEqual(0);
});
test("Creating a duplicate folder should return a 400 error", async () => {
const newFolder = await createFolder({ name: "folder-duplicate", path: "/level1/level2" });
const res = await testServer.inject({
method: "POST",
url: `/api/v1/folders`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
name: "folder-duplicate",
path: "/level1/level2"
}
});
expect(res.statusCode).toBe(400);
const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("error");
await deleteFolder({ path: "/level1/level2", id: newFolder.id });
});
});
@@ -18,7 +18,7 @@ const createFolder = async (dto: { path: string; name: string }) => {
return res.json().folder;
};
const deleteFolder = async (dto: { path: string; id: string }) => {
const deleteFolder = async (dto: { path: string; id: string; forceDelete?: boolean }) => {
const res = await testServer.inject({
method: "DELETE",
url: `/api/v2/folders/${dto.id}`,
@@ -28,7 +28,8 @@ const deleteFolder = async (dto: { path: string; id: string }) => {
body: {
projectId: seedData1.project.id,
environment: seedData1.environment.slug,
path: dto.path
path: dto.path,
forceDelete: dto.forceDelete ?? false
}
});
expect(res.statusCode).toBe(200);
@@ -40,7 +41,7 @@ describe("Secret Folder Router", async () => {
{ name: "folder1", path: "/" }, // one in root
{ name: "folder1", path: "/level1/level2" }, // then create a deep one creating intermediate ones
{ name: "folder2", path: "/" },
{ name: "folder1", path: "/level1/level2" } // this should not create folder return same thing
{ name: "folder3", path: "/level1/level2" }
])("Create folder $name in $path", async ({ name, path }) => {
const createdFolder = await createFolder({ path, name });
// check for default environments
@@ -57,7 +58,7 @@ describe("Secret Folder Router", async () => {
{
path: "/",
expected: {
folders: [{ name: "folder1" }, { name: "level1" }, { name: "folder2" }],
folders: [{ name: "folder4" }, { name: "level2" }, { name: "folder5" }],
length: 3
}
},
@@ -86,7 +87,7 @@ describe("Secret Folder Router", async () => {
folders: expect.arrayContaining(expected.folders.map((el) => expect.objectContaining(el)))
});
await Promise.all(newFolders.map(({ id }) => deleteFolder({ path, id })));
await Promise.all(newFolders.map(({ id }) => deleteFolder({ path, id, forceDelete: true })));
});
test("Update a deep folder", async () => {
@@ -162,4 +163,26 @@ describe("Secret Folder Router", async () => {
expect(updatedFolderList).toHaveProperty("folders");
expect(updatedFolderList.folders.length).toEqual(0);
});
test("Creating a duplicate folder should return a 400 error", async () => {
const newFolder = await createFolder({ name: "folder-duplicate", path: "/level1/level2" });
const res = await testServer.inject({
method: "POST",
url: `/api/v2/folders`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
projectId: seedData1.project.id,
environment: seedData1.environment.slug,
name: "folder-duplicate",
path: "/level1/level2"
}
});
expect(res.statusCode).toBe(400);
const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("error");
await deleteFolder({ path: "/level1/level2", id: newFolder.id });
});
});
+8
View File
@@ -530,6 +530,9 @@ import {
TUsers,
TUsersInsert,
TUsersUpdate,
TVaultExternalMigrationConfigs,
TVaultExternalMigrationConfigsInsert,
TVaultExternalMigrationConfigsUpdate,
TWebhooks,
TWebhooksInsert,
TWebhooksUpdate,
@@ -1377,5 +1380,10 @@ declare module "knex/types/tables" {
TAdditionalPrivilegesInsert,
TAdditionalPrivilegesUpdate
>;
[TableName.VaultExternalMigrationConfig]: KnexOriginal.CompositeTableType<
TVaultExternalMigrationConfigs,
TVaultExternalMigrationConfigsInsert,
TVaultExternalMigrationConfigsUpdate
>;
}
}
@@ -0,0 +1,29 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.VaultExternalMigrationConfig))) {
await knex.schema.createTable(TableName.VaultExternalMigrationConfig, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.string("namespace").notNullable();
t.uuid("connectionId");
t.foreign("connectionId").references("id").inTable(TableName.AppConnection);
t.timestamps(true, true, true);
t.unique(["orgId", "namespace"]);
});
await createOnUpdateTrigger(knex, TableName.VaultExternalMigrationConfig);
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.VaultExternalMigrationConfig);
await dropOnUpdateTrigger(knex, TableName.VaultExternalMigrationConfig);
}
+1
View File
@@ -180,5 +180,6 @@ export * from "./user-aliases";
export * from "./user-encryption-keys";
export * from "./user-group-membership";
export * from "./users";
export * from "./vault-external-migration-configs";
export * from "./webhooks";
export * from "./workflow-integrations";
+3 -1
View File
@@ -207,7 +207,9 @@ export enum TableName {
PamFolder = "pam_folders",
PamResource = "pam_resources",
PamAccount = "pam_accounts",
PamSession = "pam_sessions"
PamSession = "pam_sessions",
VaultExternalMigrationConfig = "vault_external_migration_configs"
}
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>
>;
+2 -2
View File
@@ -129,7 +129,7 @@ export const registerKmipSpecRouter = async (server: FastifyZodProvider) => {
id: z.string(),
value: z.string(),
algorithm: z.string(),
kmipMetadata: z.record(z.any()).optional()
kmipMetadata: z.record(z.any()).nullish()
})
}
},
@@ -435,7 +435,7 @@ export const registerKmipSpecRouter = async (server: FastifyZodProvider) => {
key: z.string(),
name: z.string(),
algorithm: z.nativeEnum(SymmetricKeyAlgorithm),
kmipMetadata: z.record(z.any()).optional()
kmipMetadata: z.record(z.any()).nullish()
}),
response: {
200: z.object({
+1 -1
View File
@@ -78,7 +78,7 @@ export type TKmipRegisterDTO = {
name: string;
key: string;
algorithm: SymmetricKeyAlgorithm;
kmipMetadata?: Record<string, unknown>;
kmipMetadata?: Record<string, unknown> | null;
} & KmipOperationBaseDTO;
export type TSetupOrgKmipDTO = {
@@ -214,6 +214,20 @@ export const licenseServiceFactory = ({
const identityUsed = await licenseDAL.countOrgUsersAndIdentities(orgId);
currentPlan.identitiesUsed = identityUsed;
if (currentPlan.identityLimit && currentPlan.identityLimit !== identityUsed) {
try {
await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, {
quantity: membersUsed,
quantityIdentities: identityUsed
});
} catch (error) {
logger.error(
error,
`Update seats used: encountered an error when updating plan for customer [customerId=${org.customerId}]`
);
}
}
await keyStore.setItemWithExpiry(
FEATURE_CACHE_KEY(org.id),
LICENSE_SERVER_CLOUD_PLAN_TTL,
+85 -84
View File
@@ -123,16 +123,16 @@ export const IDENTITIES = {
hasDeleteProtection: "Prevents deletion of the identity when enabled."
},
UPDATE: {
identityId: "The ID of the identity to update.",
identityId: "The ID of the machine identity to update.",
name: "The new name of the identity.",
role: "The new role of the identity.",
hasDeleteProtection: "Prevents deletion of the identity when enabled."
},
DELETE: {
identityId: "The ID of the identity to delete."
identityId: "The ID of the machine identity to delete."
},
GET_BY_ID: {
identityId: "The ID of the identity to get details.",
identityId: "The ID of the machine identity to get details.",
orgId: "The ID of the org of the identity"
},
LIST: {
@@ -157,7 +157,7 @@ export const UNIVERSAL_AUTH = {
clientSecret: "Your Machine Identity Client Secret."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
clientSecretTrustedIps:
"A list of IPs or CIDR ranges that the Client Secret can be used from together with the Client ID to get back an access token. You can use 0.0.0.0/0, to allow usage from any network address.",
accessTokenTrustedIps:
@@ -176,13 +176,13 @@ export const UNIVERSAL_AUTH = {
"How long to wait from the most recent failed login until resetting the lockout counter."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
@@ -196,25 +196,25 @@ export const UNIVERSAL_AUTH = {
"How long to wait from the most recent failed login until resetting the lockout counter."
},
CREATE_CLIENT_SECRET: {
identityId: "The ID of the identity to create a client secret for.",
identityId: "The ID of the machine identity to create a client secret for.",
description: "The description of the client secret.",
numUsesLimit:
"The maximum number of times that the client secret can be used; a value of 0 implies infinite number of uses.",
ttl: "The lifetime for the client secret in seconds."
},
LIST_CLIENT_SECRETS: {
identityId: "The ID of the identity to list client secrets for."
identityId: "The ID of the machine identity to list client secrets for."
},
GET_CLIENT_SECRET: {
identityId: "The ID of the identity to get the client secret from.",
identityId: "The ID of the machine identity to get the client secret from.",
clientSecretId: "The ID of the client secret to get details."
},
REVOKE_CLIENT_SECRET: {
identityId: "The ID of the identity to revoke the client secret from.",
identityId: "The ID of the machine identity to revoke the client secret from.",
clientSecretId: "The ID of the client secret to revoke."
},
CLEAR_CLIENT_LOCKOUTS: {
identityId: "The ID of the identity to clear the client lockouts from."
identityId: "The ID of the machine identity to clear the client lockouts from."
},
RENEW_ACCESS_TOKEN: {
accessToken: "The access token to renew."
@@ -226,13 +226,13 @@ export const UNIVERSAL_AUTH = {
export const LDAP_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
identityId: "The ID of the machine identity to login.",
username: "The username of the LDAP user to login.",
password: "The password of the LDAP user to login."
},
ATTACH: {
templateId: "The ID of the identity auth template to attach the configuration onto.",
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
url: "The URL of the LDAP server.",
allowedFields:
"The comma-separated array of key/value pairs of required fields that the LDAP entry must have in order to authenticate.",
@@ -252,7 +252,7 @@ export const LDAP_AUTH = {
"How long to wait from the most recent failed login until resetting the lockout counter."
},
UPDATE: {
identityId: "The ID of the identity to update the configuration for.",
identityId: "The ID of the machine identity to update the configuration for.",
url: "The new URL of the LDAP server.",
allowedFields: "The comma-separated list of allowed fields to return from the LDAP user.",
searchBase: "The new base DN to search for the LDAP user.",
@@ -272,19 +272,19 @@ export const LDAP_AUTH = {
"How long to wait from the most recent failed login until resetting the lockout counter."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the configuration for."
identityId: "The ID of the machine identity to retrieve the configuration for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the configuration for."
identityId: "The ID of the machine identity to revoke the configuration for."
},
CLEAR_CLIENT_LOCKOUTS: {
identityId: "The ID of the identity to clear the client lockouts from."
identityId: "The ID of the machine identity to clear the client lockouts from."
}
} as const;
export const ALICLOUD_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
identityId: "The ID of the machine identity to login.",
Action: "The Alibaba Cloud API action. For STS GetCallerIdentity, this should be 'GetCallerIdentity'.",
Format: "The response format. For STS GetCallerIdentity, this should be 'JSON'.",
Version: "The API version. This should be in 'YYYY-MM-DD' format (e.g., '2015-04-01').",
@@ -296,7 +296,7 @@ export const ALICLOUD_AUTH = {
Signature: "The signature string calculated based on the request parameters and AccessKey Secret."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
allowedArns: "The comma-separated list of trusted ARNs that are allowed to authenticate with Infisical.",
accessTokenTTL: "The lifetime for an access token in seconds.",
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
@@ -304,7 +304,7 @@ export const ALICLOUD_AUTH = {
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
allowedArns: "The comma-separated list of trusted ARNs that are allowed to authenticate with Infisical.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
@@ -312,19 +312,19 @@ export const ALICLOUD_AUTH = {
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
}
} as const;
export const TLS_CERT_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login."
identityId: "The ID of the machine identity to login."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
allowedCommonNames:
"The comma-separated list of trusted common names that are allowed to authenticate with Infisical.",
caCertificate: "The PEM-encoded CA certificate to validate client certificates.",
@@ -334,7 +334,7 @@ export const TLS_CERT_AUTH = {
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
allowedCommonNames:
"The comma-separated list of trusted common names that are allowed to authenticate with Infisical.",
caCertificate: "The PEM-encoded CA certificate to validate client certificates.",
@@ -344,16 +344,16 @@ export const TLS_CERT_AUTH = {
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
}
} as const;
export const AWS_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
identityId: "The ID of the machine identity to login.",
iamHttpRequestMethod: "The HTTP request method used in the signed request.",
iamRequestUrl:
"The base64-encoded HTTP URL used in the signed request. Most likely, the base64-encoding of https://sts.amazonaws.com/.",
@@ -362,7 +362,7 @@ export const AWS_AUTH = {
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
allowedPrincipalArns:
"The comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical.",
allowedAccountIds:
@@ -374,7 +374,7 @@ export const AWS_AUTH = {
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
allowedPrincipalArns:
"The new comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical.",
allowedAccountIds:
@@ -386,21 +386,21 @@ export const AWS_AUTH = {
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
}
} as const;
export const OCI_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
identityId: "The ID of the machine identity to login.",
userOcid: "The OCID of the user attempting login.",
headers: "The headers of the signed request."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
tenancyOcid: "The OCID of your tenancy.",
allowedUsernames:
"The comma-separated list of trusted OCI account usernames that are allowed to authenticate with Infisical.",
@@ -410,7 +410,7 @@ export const OCI_AUTH = {
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
tenancyOcid: "The OCID of your tenancy.",
allowedUsernames:
"The comma-separated list of trusted OCI account usernames that are allowed to authenticate with Infisical.",
@@ -420,19 +420,19 @@ export const OCI_AUTH = {
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
}
} as const;
export const AZURE_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login."
identityId: "The ID of the machine identity to login."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
tenantId: "The tenant ID for the Azure AD organization.",
resource: "The resource URL for the application registered in Azure AD.",
allowedServicePrincipalIds:
@@ -443,7 +443,7 @@ export const AZURE_AUTH = {
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
tenantId: "The new tenant ID for the Azure AD organization.",
resource: "The new resource URL for the application registered in Azure AD.",
allowedServicePrincipalIds:
@@ -454,19 +454,19 @@ export const AZURE_AUTH = {
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
}
} as const;
export const GCP_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login."
identityId: "The ID of the machine identity to login."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
allowedServiceAccounts:
"The comma-separated list of trusted service account emails corresponding to the GCE resource(s) allowed to authenticate with Infisical.",
allowedProjects:
@@ -479,7 +479,7 @@ export const GCP_AUTH = {
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
allowedServiceAccounts:
"The new comma-separated list of trusted service account emails corresponding to the GCE resource(s) allowed to authenticate with Infisical.",
allowedProjects:
@@ -492,19 +492,19 @@ export const GCP_AUTH = {
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
}
} as const;
export const KUBERNETES_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login."
identityId: "The ID of the machine identity to login."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
kubernetesHost: "The host string, host:port pair, or URL to the base of the Kubernetes API server.",
caCert: "The PEM-encoded CA cert for the Kubernetes API server.",
tokenReviewerJwt:
@@ -523,7 +523,7 @@ export const KUBERNETES_AUTH = {
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
kubernetesHost: "The new host string, host:port pair, or URL to the base of the Kubernetes API server.",
caCert: "The new PEM-encoded CA cert for the Kubernetes API server.",
tokenReviewerJwt:
@@ -542,41 +542,41 @@ export const KUBERNETES_AUTH = {
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
}
} as const;
export const TOKEN_AUTH = {
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The lifetime for an access token in seconds.",
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
},
GET_TOKENS: {
identityId: "The ID of the identity to list token metadata for.",
identityId: "The ID of the machine identity to list token metadata for.",
offset: "The offset to start from. If you enter 10, it will start from the 10th token.",
limit: "The number of tokens to return."
},
CREATE_TOKEN: {
identityId: "The ID of the identity to create the token for.",
identityId: "The ID of the machine identity to create the token for.",
name: "The name of the token to create."
},
UPDATE_TOKEN: {
@@ -590,10 +590,10 @@ export const TOKEN_AUTH = {
export const OIDC_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login."
identityId: "The ID of the machine identity to login."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
oidcDiscoveryUrl: "The URL used to retrieve the OpenID Connect configuration from the identity provider.",
caCert: "The PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints.",
boundIssuer: "The unique identifier of the identity provider issuing the JWT.",
@@ -607,7 +607,7 @@ export const OIDC_AUTH = {
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
oidcDiscoveryUrl: "The new URL used to retrieve the OpenID Connect configuration from the identity provider.",
caCert: "The new PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints.",
boundIssuer: "The new unique identifier of the identity provider issuing the JWT.",
@@ -621,19 +621,19 @@ export const OIDC_AUTH = {
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
}
} as const;
export const JWT_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login."
identityId: "The ID of the machine identity to login."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
identityId: "The ID of the machine identity to attach the configuration onto.",
configurationType: "The configuration for validating JWTs. Must be one of: 'jwks', 'static'",
jwksUrl:
"The URL of the JWKS endpoint. Required if configurationType is 'jwks'. This endpoint must serve JSON Web Key Sets (JWKS) containing the public keys used to verify JWT signatures.",
@@ -650,7 +650,7 @@ export const JWT_AUTH = {
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
identityId: "The ID of the machine identity to update the auth method for.",
configurationType: "The new configuration for validating JWTs. Must be one of: 'jwks', 'static'",
jwksUrl:
"The new URL of the JWKS endpoint. This endpoint must serve JSON Web Key Sets (JWKS) containing the public keys used to verify JWT signatures.",
@@ -667,10 +667,10 @@ export const JWT_AUTH = {
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
identityId: "The ID of the machine identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
identityId: "The ID of the machine identity to revoke the auth method for."
}
} as const;
@@ -854,12 +854,12 @@ export const PROJECT_IDENTITIES = {
search: "The text string that identity membership names will be filtered by."
},
GET_IDENTITY_MEMBERSHIP_BY_ID: {
identityId: "The ID of the identity to get the membership for.",
identityId: "The ID of the machine identity to get the membership for.",
projectId: "The ID of the project to get the identity membership for."
},
UPDATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to update the identity membership for.",
identityId: "The ID of the identity to update the membership for.",
identityId: "The ID of the machine identity to update the membership for.",
roles: {
description: "A list of role slugs to assign to the identity project membership.",
role: "The role slug to assign to the newly created identity project membership.",
@@ -872,11 +872,11 @@ export const PROJECT_IDENTITIES = {
},
DELETE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to delete the identity membership from.",
identityId: "The ID of the identity to delete the membership from."
identityId: "The ID of the machine identity to delete the membership from."
},
CREATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to create the identity membership from.",
identityId: "The ID of the identity to create the membership from.",
identityId: "The ID of the machine identity to create the membership from.",
role: "The role slug to assign to the newly created identity project membership.",
roles: {
description: "A list of role slugs to assign to the newly created identity project membership.",
@@ -950,7 +950,8 @@ export const FOLDERS = {
projectId: "The ID of the project to delete the folder from.",
environment: "The slug of the environment where the folder is located.",
directory: "The directory of the folder to delete. (Deprecated in favor of path)",
path: "The path of the folder to delete."
path: "The path of the folder to delete.",
forceDelete: "Whether to force delete the folder even if it contains resources."
}
} as const;
@@ -1271,7 +1272,7 @@ export const SECRET_TAGS = {
export const IDENTITY_ADDITIONAL_PRIVILEGE = {
CREATE: {
projectSlug: "The slug of the project of the identity in.",
identityId: "The ID of the identity to create.",
identityId: "The ID of the machine identity to create.",
slug: "The slug of the privilege to create.",
permissions: `@deprecated - use privilegePermission
The permission object for the privilege.
@@ -1297,7 +1298,7 @@ The permission object for the privilege.
},
UPDATE: {
projectSlug: "The slug of the project of the identity in.",
identityId: "The ID of the identity to update.",
identityId: "The ID of the machine identity to update.",
slug: "The slug of the privilege to update.",
newSlug: "The new slug of the privilege to update.",
permissions: `@deprecated - use privilegePermission
@@ -1323,17 +1324,17 @@ The permission object for the privilege.
},
DELETE: {
projectSlug: "The slug of the project of the identity in.",
identityId: "The ID of the identity to delete.",
identityId: "The ID of the machine identity to delete.",
slug: "The slug of the privilege to delete."
},
GET_BY_SLUG: {
projectSlug: "The slug of the project of the identity in.",
identityId: "The ID of the identity to list.",
identityId: "The ID of the machine identity to list.",
slug: "The slug of the privilege."
},
LIST: {
projectSlug: "The slug of the project of the identity in.",
identityId: "The ID of the identity to list.",
identityId: "The ID of the machine identity to list.",
unpacked: "Whether the system should send the permissions as unpacked."
}
};
@@ -1375,7 +1376,7 @@ export const PROJECT_USER_ADDITIONAL_PRIVILEGE = {
export const IDENTITY_ADDITIONAL_PRIVILEGE_V2 = {
CREATE: {
identityId: "The ID of the identity to create the privilege for.",
identityId: "The ID of the machine identity to create the privilege for.",
projectId: "The ID of the project of the identity in.",
slug: "The slug of the privilege to create.",
permission: "The permission for the privilege.",
@@ -1386,7 +1387,7 @@ export const IDENTITY_ADDITIONAL_PRIVILEGE_V2 = {
},
UPDATE: {
id: "The ID of the identity privilege.",
identityId: "The ID of the identity to update.",
identityId: "The ID of the machine identity to update.",
slug: "The slug of the privilege to update.",
privilegePermission: "The permission for the privilege.",
isTemporary: "Whether the privilege is temporary.",
@@ -1396,12 +1397,12 @@ export const IDENTITY_ADDITIONAL_PRIVILEGE_V2 = {
},
DELETE: {
id: "The ID of the identity privilege.",
identityId: "The ID of the identity to delete.",
identityId: "The ID of the machine identity to delete.",
slug: "The slug of the privilege to delete."
},
GET_BY_SLUG: {
projectSlug: "The slug of the project of the identity in.",
identityId: "The ID of the identity to list.",
identityId: "The ID of the machine identity to list.",
slug: "The slug of the privilege."
},
GET_BY_ID: {
@@ -1409,7 +1410,7 @@ export const IDENTITY_ADDITIONAL_PRIVILEGE_V2 = {
},
LIST: {
projectId: "The ID of the project that the identity is in.",
identityId: "The ID of the identity to list."
identityId: "The ID of the machine identity to list."
}
};
+17 -8
View File
@@ -184,6 +184,7 @@ import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-gr
import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
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 { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal";
import { folderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal";
@@ -542,6 +543,8 @@ export const registerRoutes = async (
const membershipRoleDAL = membershipRoleDALFactory(db);
const roleDAL = roleDALFactory(db);
const vaultExternalMigrationConfigDAL = vaultExternalMigrationConfigDALFactory(db);
const eventBusService = eventBusFactory(server.redis);
const sseService = sseServiceFactory(eventBusService, server.redis);
@@ -1371,7 +1374,8 @@ export const registerRoutes = async (
projectDAL,
folderCommitService,
secretApprovalPolicyService,
secretV2BridgeDAL
secretV2BridgeDAL,
dynamicSecretDAL
});
const secretImportService = secretImportServiceFactory({
@@ -1908,13 +1912,6 @@ export const registerRoutes = async (
notificationService
});
const migrationService = externalMigrationServiceFactory({
externalMigrationQueue,
userDAL,
permissionService,
gatewayService
});
const externalGroupOrgRoleMappingService = externalGroupOrgRoleMappingServiceFactory({
permissionService,
licenseService,
@@ -2252,6 +2249,18 @@ export const registerRoutes = async (
kmsService
});
const migrationService = externalMigrationServiceFactory({
externalMigrationQueue,
userDAL,
permissionService,
gatewayService,
kmsService,
appConnectionService,
vaultExternalMigrationConfigDAL,
secretService,
auditLogService
});
// setup the communication with license key server
await licenseService.init();
@@ -318,7 +318,8 @@ export const registerDeprecatedSecretFolderRouter = async (server: FastifyZodPro
...req.body,
projectId: req.body.workspaceId,
idOrName: req.params.folderIdOrName,
path
path,
forceDelete: true
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -13,7 +13,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Renew access token",
description: "Renew machine identity access token",
body: z.object({
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.RENEW_ACCESS_TOKEN.accessToken)
}),
@@ -48,7 +48,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Revoke access token",
description: "Revoke machine identity access token",
body: z.object({
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.REVOKE_ACCESS_TOKEN.accessToken)
}),
@@ -21,7 +21,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Login with Alibaba Cloud Auth",
description: "Login with Alibaba Cloud Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(ALICLOUD_AUTH.LOGIN.identityId),
Action: z.enum(["GetCallerIdentity"]).describe(ALICLOUD_AUTH.LOGIN.Action),
@@ -108,7 +108,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Attach Alibaba Cloud Auth configuration onto identity",
description: "Attach Alibaba Cloud Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -200,7 +200,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Update Alibaba Cloud Auth configuration on identity",
description: "Update Alibaba Cloud Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -292,7 +292,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Retrieve Alibaba Cloud Auth configuration on identity",
description: "Retrieve Alibaba Cloud Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -340,7 +340,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Delete Alibaba Cloud Auth configuration on identity",
description: "Delete Alibaba Cloud Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -23,7 +23,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Login with AWS Auth",
description: "Login with AWS Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(AWS_AUTH.LOGIN.identityId),
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
@@ -75,7 +75,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Attach AWS Auth configuration onto identity",
description: "Attach AWS Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -171,7 +171,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Update AWS Auth configuration on identity",
description: "Update AWS Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -255,7 +255,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Retrieve AWS Auth configuration on identity",
description: "Retrieve AWS Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -303,7 +303,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Delete AWS Auth configuration on identity",
description: "Delete AWS Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -20,7 +20,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Login with Azure Auth",
description: "Login with Azure Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(AZURE_AUTH.LOGIN.identityId),
jwt: z.string()
@@ -70,7 +70,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Attach Azure Auth configuration onto identity",
description: "Attach Azure Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -165,7 +165,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Update Azure Auth configuration on identity",
description: "Update Azure Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -255,7 +255,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Retrieve Azure Auth configuration on identity",
description: "Retrieve Azure Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -304,7 +304,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Delete Azure Auth configuration on identity",
description: "Delete Azure Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -20,7 +20,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Login with GCP Auth",
description: "Login with GCP Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(GCP_AUTH.LOGIN.identityId),
jwt: z.string()
@@ -70,7 +70,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Attach GCP Auth configuration onto identity",
description: "Attach GCP Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -163,7 +163,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Update GCP Auth configuration on identity",
description: "Update GCP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -249,7 +249,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Retrieve GCP Auth configuration on identity",
description: "Retrieve GCP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -298,7 +298,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Delete GCP Auth configuration on identity",
description: "Delete GCP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -96,7 +96,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Login with JWT Auth",
description: "Login with JWT Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(JWT_AUTH.LOGIN.identityId),
jwt: z.string().trim()
@@ -148,7 +148,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Attach JWT Auth configuration onto identity",
description: "Attach JWT Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -217,7 +217,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Update JWT Auth configuration on identity",
description: "Update JWT Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -283,7 +283,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Retrieve JWT Auth configuration on identity",
description: "Retrieve JWT Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -332,7 +332,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Delete JWT Auth configuration on identity",
description: "Delete JWT Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -41,7 +41,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Login with Kubernetes Auth",
description: "Login with Kubernetes Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(KUBERNETES_AUTH.LOGIN.identityId),
jwt: z.string().trim()
@@ -93,7 +93,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Attach Kubernetes Auth configuration onto identity",
description: "Attach Kubernetes Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -240,7 +240,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Update Kubernetes Auth configuration on identity",
description: "Update Kubernetes Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -383,7 +383,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Retrieve Kubernetes Auth configuration on identity",
description: "Retrieve Kubernetes Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -432,7 +432,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Delete Kubernetes Auth configuration on identity",
description: "Delete Kubernetes Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -120,7 +120,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Login with LDAP Auth",
description: "Login with LDAP Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(LDAP_AUTH.LOGIN.identityId),
username: z.string().describe(LDAP_AUTH.LOGIN.username),
@@ -198,7 +198,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Attach LDAP Auth configuration onto identity",
description: "Attach LDAP Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -389,7 +389,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Update LDAP Auth configuration on identity",
description: "Update LDAP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -510,7 +510,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Retrieve LDAP Auth configuration on identity",
description: "Retrieve LDAP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -568,7 +568,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Delete LDAP Auth configuration on identity",
description: "Delete LDAP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -621,7 +621,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Clear LDAP Auth Lockouts for identity",
description: "Clear LDAP Auth Lockouts for machine identity",
security: [
{
bearerAuth: []
@@ -20,7 +20,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Login with OCI Auth",
description: "Login with OCI Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(OCI_AUTH.LOGIN.identityId),
userOcid: z.string().trim().describe(OCI_AUTH.LOGIN.userOcid),
@@ -87,7 +87,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Attach OCI Auth configuration onto identity",
description: "Attach OCI Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -176,7 +176,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Update OCI Auth configuration on identity",
description: "Update OCI Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -259,7 +259,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Retrieve OCI Auth configuration on identity",
description: "Retrieve OCI Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -307,7 +307,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Delete OCI Auth configuration on identity",
description: "Delete OCI Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -44,7 +44,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Login with OIDC Auth",
description: "Login with OIDC Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(OIDC_AUTH.LOGIN.identityId),
jwt: z.string().trim()
@@ -100,7 +100,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Attach OIDC Auth configuration onto identity",
description: "Attach OIDC Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -201,7 +201,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Update OIDC Auth configuration on identity",
description: "Update OIDC Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -300,7 +300,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Retrieve OIDC Auth configuration on identity",
description: "Retrieve OIDC Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -349,7 +349,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Delete OIDC Auth configuration on identity",
description: "Delete OIDC Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -34,7 +34,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Create identity",
description: "Create machine identity",
security: [
{
bearerAuth: []
@@ -109,7 +109,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Update identity",
description: "Update machine identity",
security: [
{
bearerAuth: []
@@ -173,7 +173,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Delete identity",
description: "Delete machine identity",
security: [
{
bearerAuth: []
@@ -222,7 +222,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Get an identity by id",
description: "Get a machine identity by id",
security: [
{
bearerAuth: []
@@ -280,7 +280,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "List identities",
description: "List machine identities",
security: [
{
bearerAuth: []
@@ -330,7 +330,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Search identities",
description: "Search machine identities",
security: [
{
bearerAuth: []
@@ -427,7 +427,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "List project memberships that identity with id is part of",
description: "List project memberships that machine identity with id is part of",
security: [
{
bearerAuth: []
@@ -44,7 +44,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Login with TLS Certificate Auth",
description: "Login with TLS Certificate Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(TLS_CERT_AUTH.LOGIN.identityId)
}),
@@ -102,7 +102,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Attach TLS Certificate Auth configuration onto identity",
description: "Attach TLS Certificate Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -203,7 +203,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Update TLS Certificate Auth configuration on identity",
description: "Update TLS Certificate Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -304,7 +304,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Retrieve TLS Certificate Auth configuration on identity",
description: "Retrieve TLS Certificate Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -354,7 +354,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Delete TLS Certificate Auth configuration on identity",
description: "Delete TLS Certificate Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -20,7 +20,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Attach Token Auth configuration onto identity",
description: "Attach Token Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -112,7 +112,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Update Token Auth configuration on identity",
description: "Update Token Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -198,7 +198,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Retrieve Token Auth configuration on identity",
description: "Retrieve Token Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -247,7 +247,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Delete Token Auth configuration on identity",
description: "Delete Token Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -297,7 +297,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Create token for identity with Token Auth",
description: "Create token for machine identity with Token Auth",
security: [
{
bearerAuth: []
@@ -361,7 +361,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Get tokens for identity with Token Auth",
description: "Get tokens for machine identity with Token Auth",
security: [
{
bearerAuth: []
@@ -416,7 +416,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Update token for identity with Token Auth",
description: "Update token for machine identity with Token Auth",
security: [
{
bearerAuth: []
@@ -472,7 +472,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Revoke token for identity with Token Auth",
description: "Revoke token for machine identity with Token Auth",
security: [
{
bearerAuth: []
@@ -32,7 +32,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Login with Universal Auth",
description: "Login with Universal Auth for machine identity",
body: z.object({
clientId: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientId),
clientSecret: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientSecret)
@@ -90,7 +90,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Attach Universal Auth configuration onto identity",
description: "Attach Universal Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -208,7 +208,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Update Universal Auth configuration on identity",
description: "Update Universal Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -331,7 +331,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Retrieve Universal Auth configuration on identity",
description: "Retrieve Universal Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -380,7 +380,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Delete Universal Auth configuration on identity",
description: "Delete Universal Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -429,7 +429,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Create Universal Auth Client Secret for identity",
description: "Create Universal Auth Client Secret for machine identity",
security: [
{
bearerAuth: []
@@ -487,7 +487,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "List Universal Auth Client Secrets for identity",
description: "List Universal Auth Client Secrets for machine identity",
security: [
{
bearerAuth: []
@@ -537,7 +537,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Get Universal Auth Client Secret for identity",
description: "Get Universal Auth Client Secret for machine identity",
security: [
{
bearerAuth: []
@@ -567,7 +567,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
...req.auditLogInfo,
orgId: clientSecretData.orgId,
event: {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID,
metadata: {
identityId: clientSecretData.identityId,
clientSecretId: clientSecretData.id
@@ -589,7 +589,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Revoke Universal Auth Client Secrets for identity",
description: "Revoke Universal Auth Client Secrets for machine identity",
security: [
{
bearerAuth: []
@@ -641,7 +641,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Clear Universal Auth Lockouts for identity",
description: "Clear Universal Auth Lockouts for machine identity",
security: [
{
bearerAuth: []
@@ -263,7 +263,8 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.DELETE.path)
.describe(FOLDERS.DELETE.path),
forceDelete: z.boolean().optional().default(false).describe(FOLDERS.DELETE.forceDelete)
}),
response: {
200: z.object({
@@ -279,7 +280,8 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
idOrName: req.params.folderIdOrName
idOrName: req.params.folderIdOrName,
forceDelete: req.body.forceDelete
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -7,6 +7,7 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import {
ExternalMigrationProviders,
VaultImportStatus,
VaultMappingType
} from "@app/services/external-migration/external-migration-types";
@@ -113,4 +114,366 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
return { enabled };
}
});
server.route({
method: "GET",
url: "/vault/configs",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
configs: z
.object({
id: z.string(),
orgId: z.string(),
namespace: z.string(),
connectionId: z.string().nullish(),
createdAt: z.date(),
updatedAt: z.date()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const configs = await server.services.migration.getVaultExternalMigrationConfigs({
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
});
return { config };
}
});
server.route({
method: "PUT",
url: "/vault/configs/:id",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
id: z.string()
}),
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.updateVaultExternalMigration({
id: req.params.id,
...req.body,
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 };
}
});
server.route({
method: "GET",
url: "/vault/namespaces",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
namespaces: z.array(z.object({ id: z.string(), name: z.string() }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const namespaces = await server.services.migration.getVaultNamespaces({
actor: req.permission
});
return { namespaces };
}
});
server.route({
method: "GET",
url: "/vault/policies",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string()
}),
response: {
200: z.object({
policies: z.array(z.object({ name: z.string(), rules: z.string() }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const policies = await server.services.migration.getVaultPolicies({
actor: req.permission,
namespace: req.query.namespace
});
return { policies };
}
});
server.route({
method: "GET",
url: "/vault/mounts",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string()
}),
response: {
200: z.object({
mounts: z.array(z.object({ path: z.string(), type: z.string(), version: z.string().nullish() }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const mounts = await server.services.migration.getVaultMounts({
actor: req.permission,
namespace: req.query.namespace
});
return { mounts };
}
});
server.route({
method: "GET",
url: "/vault/auth-mounts",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string(),
authType: z.string().optional()
}),
response: {
200: z.object({
mounts: z.array(z.object({ path: z.string(), type: z.string() }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const mounts = await server.services.migration.getVaultAuthMounts({
actor: req.permission,
namespace: req.query.namespace,
authType: req.query.authType
});
return { mounts };
}
});
server.route({
method: "POST",
url: "/vault/import-secrets",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
projectId: z.string(),
environment: z.string(),
secretPath: z.string(),
vaultNamespace: z.string(),
vaultSecretPath: z.string()
}),
response: {
200: z.object({
status: z.nativeEnum(VaultImportStatus)
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const result = await server.services.migration.importVaultSecrets({
actor: req.permission,
auditLogInfo: req.auditLogInfo,
...req.body
});
return result;
}
});
server.route({
method: "GET",
url: "/vault/secret-paths",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string(),
mountPath: z.string()
}),
response: {
200: z.object({
secretPaths: z.string().array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const secretPaths = await server.services.migration.getVaultSecretPaths({
actor: req.permission,
namespace: req.query.namespace,
mountPath: req.query.mountPath
});
return { secretPaths };
}
});
server.route({
method: "GET",
url: "/vault/auth-roles/kubernetes",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string(),
mountPath: z.string()
}),
response: {
200: z.object({
roles: z.array(
z.object({
name: z.string(),
mountPath: z.string(),
bound_service_account_names: z.array(z.string()),
bound_service_account_namespaces: z.array(z.string()),
token_ttl: z.number().optional(),
token_max_ttl: z.number().optional(),
token_policies: z.array(z.string()).optional(),
token_bound_cidrs: z.array(z.string()).optional(),
token_explicit_max_ttl: z.number().optional(),
token_no_default_policy: z.boolean().optional(),
token_num_uses: z.number().optional(),
token_period: z.number().optional(),
token_type: z.string().optional(),
audience: z.string().optional(),
alias_name_source: z.string().optional(),
config: z.object({
kubernetes_host: z.string(),
kubernetes_ca_cert: z.string().optional(),
issuer: z.string().optional(),
disable_iss_validation: z.boolean().optional(),
disable_local_ca_jwt: z.boolean().optional()
})
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const roles = await server.services.migration.getVaultKubernetesAuthRoles({
actor: req.permission,
namespace: req.query.namespace,
mountPath: req.query.mountPath
});
return { roles };
}
});
};
@@ -2,3 +2,7 @@ export enum HCVaultConnectionMethod {
AccessToken = "access-token",
AppRole = "app-role"
}
export enum HCVaultAuthType {
Kubernetes = "kubernetes"
}
@@ -12,8 +12,56 @@ import { logger } from "@app/lib/logger";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { HCVaultConnectionMethod } from "./hc-vault-connection-enums";
import { THCVaultConnection, THCVaultConnectionConfig, THCVaultMountResponse } from "./hc-vault-connection-types";
import { HCVaultAuthType, HCVaultConnectionMethod } from "./hc-vault-connection-enums";
import {
THCVaultAuthMount,
THCVaultAuthMountResponse,
THCVaultConnection,
THCVaultConnectionConfig,
THCVaultKubernetesAuthConfig,
THCVaultKubernetesAuthRole,
THCVaultKubernetesAuthRoleWithConfig,
THCVaultMount,
THCVaultMountResponse
} from "./hc-vault-connection-types";
// Concurrency limit for HC Vault API requests to avoid rate limiting
const HC_VAULT_CONCURRENCY_LIMIT = 20;
/**
* Creates a concurrency limiter that restricts the number of concurrent async operations
* @param limit - Maximum number of concurrent operations
* @returns A function that takes an async function and executes it with concurrency control
*/
const createConcurrencyLimiter = (limit: number) => {
let activeCount = 0;
const queue: Array<() => void> = [];
const next = () => {
activeCount -= 1;
if (queue.length > 0) {
const resolve = queue.shift();
resolve?.();
}
};
return async <T>(fn: () => Promise<T>): Promise<T> => {
// If we're at the limit, wait in queue
if (activeCount >= limit) {
await new Promise<void>((resolve) => {
queue.push(resolve);
});
}
activeCount += 1;
try {
return await fn();
} finally {
next();
}
};
};
export const getHCVaultInstanceUrl = async (config: THCVaultConnectionConfig) => {
const instanceUrl = removeTrailingSlash(config.credentials.instanceUrl);
@@ -181,30 +229,573 @@ export const validateHCVaultConnectionCredentials = async (
}
};
export const listHCVaultMounts = async (
export const listHCVaultPolicies = async (
namespace: string,
connection: THCVaultConnection,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
) => {
const instanceUrl = await getHCVaultInstanceUrl(connection);
const accessToken = await getHCVaultAccessToken(connection, gatewayService);
try {
const { data: listData } = await requestWithHCVaultGateway<{
data: {
policies: string[];
};
}>(connection, gatewayService, {
url: `${instanceUrl}/v1/sys/policy`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespace
}
});
const policyNames = listData.data.policies || [];
const limiter = createConcurrencyLimiter(HC_VAULT_CONCURRENCY_LIMIT);
const policies = await Promise.all(
policyNames.map((policyName) =>
limiter(async () => {
try {
const { data: policyData } = await requestWithHCVaultGateway<{
data: {
name: string;
rules: string;
};
}>(connection, gatewayService, {
url: `${instanceUrl}/v1/sys/policy/${policyName}`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespace
}
});
return {
name: policyData.data.name,
rules: policyData.data.rules
};
} catch (error: unknown) {
logger.error(error, `Unable to fetch policy details for ${policyName}`);
return {
name: policyName,
rules: ""
};
}
})
)
);
return policies;
} catch (error: unknown) {
logger.error(error, "Unable to list HC Vault policies");
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to list policies: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to list policies from HashiCorp Vault"
});
}
};
export const listHCVaultNamespaces = async (
connection: THCVaultConnection,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
) => {
const instanceUrl = await getHCVaultInstanceUrl(connection);
const accessToken = await getHCVaultAccessToken(connection, gatewayService);
const currentNamespace = connection.credentials.namespace || "/";
// Helper function to fetch namespaces at a specific path
const fetchNamespacesAtPath = async (namespacePath: string): Promise<string[] | null> => {
try {
const { data } = await requestWithHCVaultGateway<{
data: {
keys: string[];
key_info?: {
[key: string]: {
id: string;
path: string;
custom_metadata?: Record<string, unknown>;
};
};
};
}>(connection, gatewayService, {
url: `${instanceUrl}/v1/sys/namespaces?list=true`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespacePath
}
});
return data.data.keys || [];
} catch (error: unknown) {
if (error instanceof AxiosError && error.response?.status === 404) {
// No child namespaces at this path
return null;
}
throw error;
}
};
// Recursive function to get all namespaces at all depths with controlled parallelization
const recursivelyGetAllNamespaces = async (
parentPath: string,
limiter: ReturnType<typeof createConcurrencyLimiter>
): Promise<string[]> => {
const childKeys = await fetchNamespacesAtPath(parentPath);
if (childKeys === null || childKeys.length === 0) {
return [];
}
// Process namespaces in parallel with concurrency control
const namespacesArrays = await Promise.all(
childKeys.map((namespaceKey) =>
limiter(async () => {
// Remove trailing slash from the key
const cleanNamespaceKey = namespaceKey.replace(/\/$/, "");
// Build the full path
let fullNamespacePath: string;
if (parentPath === "/") {
fullNamespacePath = cleanNamespaceKey;
} else {
fullNamespacePath = `${parentPath}/${cleanNamespaceKey}`;
}
// Recursively fetch child namespaces
const childNamespaces = await recursivelyGetAllNamespaces(fullNamespacePath, limiter);
// Return this namespace and all its children
return [fullNamespacePath, ...childNamespaces];
})
)
);
// Flatten the arrays into a single array
return namespacesArrays.flat();
};
try {
// Create concurrency limiter to avoid overwhelming the Vault instance
const limiter = createConcurrencyLimiter(HC_VAULT_CONCURRENCY_LIMIT);
// Get all namespaces starting from currentNamespace
const childNamespaces = await recursivelyGetAllNamespaces(currentNamespace, limiter);
// Build the result array with full paths
const namespaces = childNamespaces.map((path) => ({
id: path,
name: path
}));
// Always include the current/root namespace
namespaces.unshift({
id: currentNamespace,
name: currentNamespace
});
return namespaces;
} catch (error: unknown) {
logger.error(error, "Unable to list HC Vault namespaces");
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to list namespaces: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to list namespaces from HashiCorp Vault"
});
}
};
export const listHCVaultMounts = async (
connection: THCVaultConnection,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
namespace?: string
) => {
const instanceUrl = await getHCVaultInstanceUrl(connection);
const accessToken = await getHCVaultAccessToken(connection, gatewayService);
const targetNamespace = namespace || connection.credentials.namespace;
const { data } = await requestWithHCVaultGateway<THCVaultMountResponse>(connection, gatewayService, {
url: `${instanceUrl}/v1/sys/mounts`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
...(connection.credentials.namespace ? { "X-Vault-Namespace": connection.credentials.namespace } : {})
...(targetNamespace ? { "X-Vault-Namespace": targetNamespace } : {})
}
});
const mounts: string[] = [];
const mounts: THCVaultMount[] = [];
// Filter for "kv" version 2 type only
Object.entries(data.data).forEach(([path, mount]) => {
if (mount.type === "kv" && mount.options?.version === "2") {
mounts.push(path);
}
mounts.push({
path,
type: mount.type,
version: mount.options?.version
});
});
return mounts;
};
export const listHCVaultSecretPaths = async (
namespace: string,
connection: THCVaultConnection,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
filterMountPath?: string
) => {
const instanceUrl = await getHCVaultInstanceUrl(connection);
const accessToken = await getHCVaultAccessToken(connection, gatewayService);
const getPaths = async (mountPath: string, secretPath: string, kvVersion: "1" | "2"): Promise<string[] | null> => {
try {
let path: string;
if (kvVersion === "2") {
// For KV v2: /v1/{mount}/metadata/{path}?list=true
path = secretPath ? `${mountPath}/metadata/${secretPath}` : `${mountPath}/metadata`;
} else {
// For KV v1: /v1/{mount}/{path}?list=true
path = secretPath ? `${mountPath}/${secretPath}` : mountPath;
}
const { data } = await requestWithHCVaultGateway<{
data: {
keys: string[];
};
}>(connection, gatewayService, {
url: `${instanceUrl}/v1/${path}?list=true`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespace
}
});
return data.data.keys;
} catch (error) {
if (error instanceof AxiosError && error.response?.status === 404) {
return null;
}
throw error;
}
};
// Recursive function to get all secret paths in a mount with controlled parallelization
const recursivelyGetAllPaths = async (
mountPath: string,
kvVersion: "1" | "2",
limiter: ReturnType<typeof createConcurrencyLimiter>,
currentPath: string = ""
): Promise<string[]> => {
const paths = await getPaths(mountPath, currentPath, kvVersion);
if (paths === null || paths.length === 0) {
return [];
}
// Process paths in parallel with concurrency control
const secretPathsArrays = await Promise.all(
paths.map((path) =>
limiter(async () => {
const cleanPath = path.endsWith("/") ? path.slice(0, -1) : path;
const fullItemPath = currentPath ? `${currentPath}/${cleanPath}` : cleanPath;
if (path.endsWith("/")) {
// it's a folder so we recurse into it
return recursivelyGetAllPaths(mountPath, kvVersion, limiter, fullItemPath);
}
// it's a secret so we return it
return [`${mountPath}/${fullItemPath}`];
})
)
);
// Flatten the arrays into a single array
return secretPathsArrays.flat();
};
// Get all mounts
const mounts = await listHCVaultMounts(connection, gatewayService, namespace);
// Filter for KV mounts (kv, kv-v1, kv-v2)
let kvMounts = mounts.filter((mount) => mount.type === "kv" || mount.type.startsWith("kv"));
// If filterMountPath is provided, filter to only that mount
if (filterMountPath) {
const normalizedFilterPath = filterMountPath.replace(/\/$/, ""); // Remove trailing slash
kvMounts = kvMounts.filter((mount) => mount.path.replace(/\/$/, "") === normalizedFilterPath);
}
// Create concurrency limiter to avoid overwhelming the Vault instance
const limiter = createConcurrencyLimiter(HC_VAULT_CONCURRENCY_LIMIT);
// Collect all secret paths from all KV mounts in parallel
const allSecretPathsArrays = await Promise.all(
kvMounts.map(async (mount) => {
const kvVersion = mount.version === "2" ? "2" : "1";
const cleanMountPath = mount.path.replace(/\/$/, ""); // Remove trailing slash
return recursivelyGetAllPaths(cleanMountPath, kvVersion, limiter);
})
);
// Flatten the arrays into a single array
const allSecretPaths = allSecretPathsArrays.flat();
return allSecretPaths;
};
export const getHCVaultSecretsForPath = async (
namespace: string,
secretPath: string,
connection: THCVaultConnection,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
) => {
const instanceUrl = await getHCVaultInstanceUrl(connection);
const accessToken = await getHCVaultAccessToken(connection, gatewayService);
try {
// Extract mount and path from the secretPath
// secretPath format: {mount}/{path}
const pathParts = secretPath.split("/");
const mountPath = pathParts[0];
const actualPath = pathParts.slice(1).join("/");
if (!mountPath || !actualPath) {
throw new BadRequestError({
message: "Invalid secret path format. Expected format: {mount}/{path}"
});
}
// Get mounts to determine KV version
const mounts = await listHCVaultMounts(connection, gatewayService, namespace);
const mount = mounts.find((m) => m.path.replace(/\/$/, "") === mountPath);
if (!mount) {
throw new BadRequestError({
message: `Mount '${mountPath}' not found in HashiCorp Vault`
});
}
const kvVersion = mount.version === "2" ? "2" : "1";
// Fetch secrets based on KV version
if (kvVersion === "2") {
// For KV v2: /v1/{mount}/data/{path}
const { data } = await requestWithHCVaultGateway<{
data: {
data: Record<string, string>; // KV v2 has nested data structure
metadata: {
created_time: string;
deletion_time: string;
destroyed: boolean;
version: number;
};
};
}>(connection, gatewayService, {
url: `${instanceUrl}/v1/${mountPath}/data/${actualPath}`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespace
}
});
return data.data.data;
}
// For KV v1: /v1/{mount}/{path}
const { data } = await requestWithHCVaultGateway<{
data: Record<string, string>; // KV v1 has flat data structure
lease_duration: number;
lease_id: string;
renewable: boolean;
}>(connection, gatewayService, {
url: `${instanceUrl}/v1/${mountPath}/${actualPath}`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespace
}
});
return data.data;
} catch (error: unknown) {
logger.error(error, "Unable to fetch secrets from HC Vault path");
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to fetch secrets: ${error.message || "Unknown error"}`
});
}
if (error instanceof BadRequestError) {
throw error;
}
throw new BadRequestError({
message: "Unable to fetch secrets from HashiCorp Vault"
});
}
};
export const getHCVaultAuthMounts = async (
namespace: string,
authType: HCVaultAuthType | undefined,
connection: THCVaultConnection,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
): Promise<THCVaultAuthMount[]> => {
const instanceUrl = await getHCVaultInstanceUrl(connection);
const accessToken = await getHCVaultAccessToken(connection, gatewayService);
try {
const { data } = await requestWithHCVaultGateway<THCVaultAuthMountResponse>(connection, gatewayService, {
url: `${instanceUrl}/v1/sys/auth`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespace
}
});
const authMounts: THCVaultAuthMount[] = [];
Object.entries(data.data).forEach(([path, authMethod]) => {
// If authType is specified, filter by it; otherwise, include all
if (!authType || authMethod.type === authType) {
authMounts.push({
path,
type: authMethod.type,
description: authMethod.description,
accessor: authMethod.accessor
});
}
});
return authMounts;
} catch (error: unknown) {
const authTypeStr = authType || "all";
logger.error(error, `Unable to list HC Vault ${authTypeStr} auth mounts`);
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to list ${authTypeStr} auth mounts: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: `Unable to list ${authTypeStr} auth mounts from HashiCorp Vault`
});
}
};
export const getHCVaultKubernetesAuthRoles = async (
namespace: string,
mountPath: string,
connection: THCVaultConnection,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
): Promise<THCVaultKubernetesAuthRoleWithConfig[]> => {
const instanceUrl = await getHCVaultInstanceUrl(connection);
const accessToken = await getHCVaultAccessToken(connection, gatewayService);
// Remove trailing slash from mount path
const cleanMountPath = mountPath.endsWith("/") ? mountPath.slice(0, -1) : mountPath;
try {
// 1. Get the Kubernetes auth configuration for this mount
const { data: configResponse } = await requestWithHCVaultGateway<{ data: THCVaultKubernetesAuthConfig }>(
connection,
gatewayService,
{
url: `${instanceUrl}/v1/auth/${cleanMountPath}/config`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespace
}
}
);
const kubernetesConfig = configResponse.data;
// 2. List all roles in this mount
const { data: roleListResponse } = await requestWithHCVaultGateway<{ data: { keys: string[] } }>(
connection,
gatewayService,
{
url: `${instanceUrl}/v1/auth/${cleanMountPath}/role`,
method: "LIST",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespace
}
}
);
const roleNames = roleListResponse.data.keys;
if (!roleNames || roleNames.length === 0) {
return [];
}
// 3. Fetch details for each role with concurrency control
const limiter = createConcurrencyLimiter(HC_VAULT_CONCURRENCY_LIMIT);
const roleDetailsPromises = roleNames.map((roleName) =>
limiter(async () => {
const { data: roleResponse } = await requestWithHCVaultGateway<{ data: THCVaultKubernetesAuthRole }>(
connection,
gatewayService,
{
url: `${instanceUrl}/v1/auth/${cleanMountPath}/role/${roleName}`,
method: "GET",
headers: {
"X-Vault-Token": accessToken,
"X-Vault-Namespace": namespace
}
}
);
// 4. Merge the role with the config
return {
...roleResponse.data,
name: roleName,
config: kubernetesConfig,
mountPath: cleanMountPath
} as THCVaultKubernetesAuthRoleWithConfig;
})
);
const roles = await Promise.all(roleDetailsPromises);
return roles;
} catch (error: unknown) {
logger.error(error, "Unable to list HC Vault Kubernetes auth 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 auth roles: ${errorMessage}`
});
}
throw new BadRequestError({
message: "Unable to list Kubernetes auth roles from HashiCorp Vault"
});
}
};
@@ -21,7 +21,8 @@ export const hcVaultConnectionService = (
try {
const mounts = await listHCVaultMounts(appConnection, gatewayService);
return mounts;
// Filter for KV version 2 mounts only and extract just the paths
return mounts.filter((mount) => mount.type === "kv" && mount.version === "2").map((mount) => mount.path);
} catch (error) {
logger.error(error, "Failed to establish connection with Hashicorp Vault");
return [];
@@ -33,3 +33,65 @@ export type THCVaultMountResponse = {
};
};
};
export type THCVaultMount = {
path: string;
type: string;
version?: string | null;
};
export type THCVaultAuthMountResponse = {
data: {
[key: string]: {
type: string;
description: string;
accessor: string;
config: {
default_lease_ttl: number;
max_lease_ttl: number;
force_no_cache: boolean;
};
local: boolean;
seal_wrap: boolean;
external_entropy_access: boolean;
options: Record<string, string> | null;
};
};
};
export type THCVaultAuthMount = {
path: string;
type: string;
description: string;
accessor: string;
};
export type THCVaultKubernetesAuthConfig = {
kubernetes_host: string;
kubernetes_ca_cert?: string;
issuer?: string;
disable_iss_validation?: boolean;
disable_local_ca_jwt?: boolean;
};
export type THCVaultKubernetesAuthRole = {
name: string;
bound_service_account_names: string[];
bound_service_account_namespaces: string[];
token_ttl?: number;
token_max_ttl?: number;
token_policies?: string[];
token_bound_cidrs?: string[];
token_explicit_max_ttl?: number;
token_no_default_policy?: boolean;
token_num_uses?: number;
token_period?: number;
token_type?: string;
audience?: string;
alias_name_source?: string;
};
export type THCVaultKubernetesAuthRoleWithConfig = THCVaultKubernetesAuthRole & {
config: THCVaultKubernetesAuthConfig;
mountPath: string;
};
@@ -1,9 +1,33 @@
import { OrgMembershipRole } from "@app/db/schemas";
import {
AuditLogInfo,
EventType,
SecretApprovalEvent,
TAuditLogServiceFactory
} from "@app/ee/services/audit-log/audit-log-types";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
import { DatabaseErrorCode } from "@app/lib/error-codes";
import { BadRequestError, DatabaseError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection/app-connection-enums";
import { decryptAppConnectionCredentials } from "../app-connection/app-connection-fns";
import { TAppConnectionServiceFactory } from "../app-connection/app-connection-service";
import {
getHCVaultAuthMounts,
getHCVaultKubernetesAuthRoles,
getHCVaultSecretsForPath,
HCVaultAuthType,
listHCVaultMounts,
listHCVaultPolicies,
listHCVaultSecretPaths,
THCVaultConnection
} from "../app-connection/hc-vault";
import { TKmsServiceFactory } from "../kms/kms-service";
import { TSecretServiceFactory } from "../secret/secret-service";
import { SecretProtectionType } from "../secret/secret-types";
import { TUserDALFactory } from "../user/user-dal";
import {
decryptEnvKeyDataFn,
@@ -15,16 +39,29 @@ import { TExternalMigrationQueueFactory } from "./external-migration-queue";
import {
ExternalMigrationProviders,
ExternalPlatforms,
TCreateVaultExternalMigrationDTO,
TDeleteVaultExternalMigrationDTO,
THasCustomVaultMigrationDTO,
TImportEnvKeyDataDTO,
TImportVaultDataDTO
TImportVaultDataDTO,
TUpdateVaultExternalMigrationDTO,
VaultImportStatus
} from "./external-migration-types";
import { TVaultExternalMigrationConfigDALFactory } from "./vault-external-migration-config-dal";
type TExternalMigrationServiceFactoryDep = {
permissionService: TPermissionServiceFactory;
secretService: TSecretServiceFactory;
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
externalMigrationQueue: TExternalMigrationQueueFactory;
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
vaultExternalMigrationConfigDAL: Pick<
TVaultExternalMigrationConfigDALFactory,
"create" | "findOne" | "transaction" | "find" | "updateById" | "deleteById" | "findById"
>;
userDAL: Pick<TUserDALFactory, "findById">;
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
};
export type TExternalMigrationServiceFactory = ReturnType<typeof externalMigrationServiceFactory>;
@@ -33,7 +70,12 @@ export const externalMigrationServiceFactory = ({
permissionService,
externalMigrationQueue,
userDAL,
gatewayService
gatewayService,
secretService,
auditLogService,
appConnectionService,
vaultExternalMigrationConfigDAL,
kmsService
}: TExternalMigrationServiceFactoryDep) => {
const importEnvKeyData = async ({
decryptionKey,
@@ -171,9 +213,554 @@ export const externalMigrationServiceFactory = ({
return actorOrgId in vaultMigrationTransformMappings;
};
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 getHCVaultAuthMounts(namespace, HCVaultAuthType.Kubernetes, connection, gatewayService);
const mounts = await listHCVaultMounts(connection, gatewayService);
const sampleKvMount = mounts.find((mount) => mount.type === "kv");
if (sampleKvMount) {
await listHCVaultSecretPaths(namespace, connection, gatewayService, sampleKvMount.path);
}
} 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(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can configure vault external migration" });
}
const connection = await appConnectionService.connectAppConnectionById<THCVaultConnection>(
AppConnection.HCVault,
connectionId,
actor
);
await validateVaultExternalMigrationConnection({
connection,
namespace
});
try {
const config = await vaultExternalMigrationConfigDAL.create({
namespace,
connectionId,
orgId: actor.orgId
});
return config;
} catch (error) {
if (
error instanceof DatabaseError &&
(error.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation
) {
throw new BadRequestError({
message: `Vault external migration already exists for this namespace`
});
}
throw error;
}
};
const updateVaultExternalMigration = async ({
id,
namespace,
connectionId,
actor
}: TUpdateVaultExternalMigrationDTO) => {
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 update vault external migration" });
}
if (connectionId) {
const connection = await appConnectionService.connectAppConnectionById<THCVaultConnection>(
AppConnection.HCVault,
connectionId,
actor
);
await validateVaultExternalMigrationConnection({
connection,
namespace
});
}
const config = await vaultExternalMigrationConfigDAL.updateById(id, {
namespace,
connectionId
});
return config;
};
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" });
}
const configs = await vaultExternalMigrationConfigDAL.find({
orgId: actor.orgId
});
return configs;
};
const getVaultNamespaces = 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 namespaces" });
}
// Get all configured namespaces for this org
const vaultConfigs = await vaultExternalMigrationConfigDAL.find({
orgId: actor.orgId
});
// Return the configured namespaces as an array of objects with id and name
// where both id and name are the namespace path
const namespaces = vaultConfigs.map((config) => ({
id: config.namespace,
name: config.namespace
}));
return namespaces;
};
const getVaultPolicies = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
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 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 getVaultMounts = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
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 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 getVaultSecretPaths = async ({
actor,
namespace,
mountPath
}: {
actor: OrgServiceActor;
namespace: string;
mountPath: string;
}) => {
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 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 importVaultSecrets = async ({
actor,
projectId,
environment,
secretPath,
vaultNamespace,
vaultSecretPath,
auditLogInfo
}: {
actor: OrgServiceActor;
projectId: string;
environment: string;
secretPath: string;
vaultNamespace: string;
vaultSecretPath: string;
auditLogInfo: AuditLogInfo;
}) => {
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 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 vaultSecrets = await getHCVaultSecretsForPath(vaultNamespace, vaultSecretPath, connection, gatewayService);
try {
const secretOperation = await secretService.createManySecretsRaw({
actorId: actor.id,
actor: actor.type,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
secretPath,
environment,
projectId,
secrets: Object.entries(vaultSecrets).map(([secretKey, secretValue]) => ({
secretKey,
secretValue
}))
});
if (secretOperation.type === SecretProtectionType.Approval) {
await auditLogService.createAuditLog({
projectId,
...auditLogInfo,
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: secretOperation.approval.committerUserId,
secretApprovalRequestId: secretOperation.approval.id,
secretApprovalRequestSlug: secretOperation.approval.slug,
secretPath,
environment,
secrets: Object.entries(vaultSecrets).map(([secretKey]) => ({
secretKey
})),
eventType: SecretApprovalEvent.CreateMany
}
}
});
return { status: VaultImportStatus.ApprovalRequired };
}
return { status: VaultImportStatus.Imported };
} catch (error) {
throw new BadRequestError({
message: `Failed to import Vault secrets. ${error instanceof Error ? error.message : "Unknown error"}`
});
}
};
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 getVaultAuthMounts = async ({
actor,
namespace,
authType
}: {
actor: OrgServiceActor;
namespace: string;
authType?: string;
}) => {
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 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 getVaultKubernetesAuthRoles = async ({
actor,
namespace,
mountPath
}: {
actor: OrgServiceActor;
namespace: string;
mountPath: string;
}) => {
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 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;
};
return {
importEnvKeyData,
importVaultData,
hasCustomVaultMigration
hasCustomVaultMigration,
createVaultExternalMigration,
getVaultExternalMigrationConfigs,
updateVaultExternalMigration,
deleteVaultExternalMigration,
getVaultNamespaces,
getVaultPolicies,
getVaultMounts,
getVaultAuthMounts,
getVaultSecretPaths,
importVaultSecrets,
getVaultKubernetesAuthRoles
};
};
@@ -1,4 +1,4 @@
import { TOrgPermission } from "@app/lib/types";
import { OrgServiceActor, TOrgPermission } from "@app/lib/types";
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
@@ -121,3 +121,26 @@ export enum ExternalMigrationProviders {
Vault = "vault",
EnvKey = "env-key"
}
export enum VaultImportStatus {
Imported = "imported",
ApprovalRequired = "approval-required"
}
export type TCreateVaultExternalMigrationDTO = {
namespace: string;
connectionId: string;
actor: OrgServiceActor;
};
export type TUpdateVaultExternalMigrationDTO = {
id: string;
namespace: string;
connectionId: string | null;
actor: OrgServiceActor;
};
export type TDeleteVaultExternalMigrationDTO = {
id: string;
actor: OrgServiceActor;
};
@@ -0,0 +1,67 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
export type TVaultExternalMigrationConfigDALFactory = ReturnType<typeof vaultExternalMigrationConfigDALFactory>;
export const vaultExternalMigrationConfigDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.VaultExternalMigrationConfig);
const findOne = async (filter: { orgId: string; namespace: string }, tx?: Knex) => {
try {
const result = await (tx || db?.replicaNode?.() || db)(TableName.VaultExternalMigrationConfig)
.leftJoin(
TableName.AppConnection,
`${TableName.AppConnection}.id`,
`${TableName.VaultExternalMigrationConfig}.connectionId`
)
/* eslint-disable @typescript-eslint/no-misused-promises */
.where(buildFindFilter(prependTableNameToFindFilter(TableName.VaultExternalMigrationConfig, filter)))
.select(selectAllTableCols(TableName.VaultExternalMigrationConfig))
.select(
db.ref("id").withSchema(TableName.AppConnection).as("appConnectionId"),
db.ref("name").withSchema(TableName.AppConnection).as("appConnectionName"),
db.ref("app").withSchema(TableName.AppConnection).as("appConnectionApp"),
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("appConnectionEncryptedCredentials"),
db.ref("orgId").withSchema(TableName.AppConnection).as("appConnectionOrgId"),
db.ref("method").withSchema(TableName.AppConnection).as("appConnectionMethod"),
db.ref("description").withSchema(TableName.AppConnection).as("appConnectionDescription"),
db.ref("version").withSchema(TableName.AppConnection).as("appConnectionVersion"),
db.ref("gatewayId").withSchema(TableName.AppConnection).as("appConnectionGatewayId"),
db.ref("projectId").withSchema(TableName.AppConnection).as("appConnectionProjectId"),
db.ref("createdAt").withSchema(TableName.AppConnection).as("appConnectionCreatedAt"),
db.ref("updatedAt").withSchema(TableName.AppConnection).as("appConnectionUpdatedAt")
)
.first();
if (!result) return undefined;
return {
...result,
connection: result.appConnectionId
? {
id: result.appConnectionId,
name: result.appConnectionName,
app: result.appConnectionApp,
encryptedCredentials: result.appConnectionEncryptedCredentials,
orgId: result.appConnectionOrgId,
method: result.appConnectionMethod,
description: result.appConnectionDescription,
version: result.appConnectionVersion,
gatewayId: result.appConnectionGatewayId,
projectId: result.appConnectionProjectId,
createdAt: result.appConnectionCreatedAt,
updatedAt: result.appConnectionUpdatedAt
}
: undefined
};
} catch (error) {
throw new DatabaseError({ error, name: "Find one" });
}
};
return { ...orm, findOne };
};
+1 -1
View File
@@ -99,5 +99,5 @@ export type TImportKeyMaterialDTO = {
projectId: string;
orgId: string;
keyUsage: KmsKeyUsage;
kmipMetadata?: Record<string, unknown>;
kmipMetadata?: Record<string, unknown> | null;
};
@@ -5,6 +5,7 @@ import path from "path";
import { v4 as uuidv4, validate as uuidValidate } from "uuid";
import { ActionProjectType, TProjectEnvironments, TSecretFolders, TSecretFoldersInsert } from "@app/db/schemas";
import { TDynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
@@ -12,6 +13,7 @@ import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/
import { PgSqlLock } from "@app/keystore/keystore";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
import { buildFolderPath } from "@app/services/secret-folder/secret-folder-fns";
import {
@@ -47,7 +49,11 @@ type TSecretFolderServiceFactoryDep = {
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
secretV2BridgeDAL: Pick<TSecretV2BridgeDALFactory, "findByFolderIds" | "invalidateSecretCacheByProjectId">;
secretV2BridgeDAL: Pick<
TSecretV2BridgeDALFactory,
"findByFolderIds" | "invalidateSecretCacheByProjectId" | "findOne"
>;
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findOne">;
};
export type TSecretFolderServiceFactory = ReturnType<typeof secretFolderServiceFactory>;
@@ -61,7 +67,8 @@ export const secretFolderServiceFactory = ({
folderCommitService,
projectDAL,
secretApprovalPolicyService,
secretV2BridgeDAL
secretV2BridgeDAL,
dynamicSecretDAL
}: TSecretFolderServiceFactoryDep) => {
const createFolder = async ({
projectId,
@@ -111,24 +118,11 @@ export const secretFolderServiceFactory = ({
});
}
// check if the exact folder already exists
const existingFolder = await folderDAL.findOne(
{
envId: env.id,
parentId: parentFolder.id,
name,
isReserved: false
},
tx
);
if (existingFolder) {
return existingFolder;
}
// exact folder case
if (parentFolder.path === pathWithFolder) {
return parentFolder;
throw new BadRequestError({
message: `Folder with name '${name}' already exists in path '${secretPath}'`
});
}
let currentParentId = parentFolder.id;
@@ -534,13 +528,19 @@ export const secretFolderServiceFactory = ({
projectId,
env,
parentId,
idOrName
idOrName,
actor
}: {
projectId: string;
env: TProjectEnvironments;
parentId: string;
idOrName: string;
actor: ActorType;
}) => {
if (actor === ActorType.IDENTITY) {
return;
}
let targetFolder = await folderDAL
.findOne({
envId: env.id,
@@ -638,7 +638,8 @@ export const secretFolderServiceFactory = ({
actorAuthMethod,
environment,
path: secretPath,
idOrName
idOrName,
forceDelete = false
}: TDeleteFolderDTO) => {
const { permission } = await permissionService.getProjectPermission({
actor,
@@ -664,7 +665,7 @@ export const secretFolderServiceFactory = ({
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`
});
await $checkFolderPolicy({ projectId, env, parentId: parentFolder.id, idOrName });
await $checkFolderPolicy({ projectId, env, parentId: parentFolder.id, idOrName, actor });
let folderToDelete = await folderDAL
.findOne({
@@ -690,6 +691,22 @@ export const secretFolderServiceFactory = ({
throw new NotFoundError({ message: `Folder with ID '${idOrName}' not found` });
}
// Check if folder contains resources (secrets, dynamic secrets, subfolders)
if (!forceDelete) {
const error = new BadRequestError({
message: `Cannot delete folder "${folderToDelete.name}" because it contains resources. Use forceDelete=true to delete it forcefully.`,
name: "deleteFolder"
});
const secretV2 = await secretV2BridgeDAL.findOne({ folderId: folderToDelete.id }).catch(() => null);
if (secretV2) throw error;
const dynamicSecret = await dynamicSecretDAL.findOne({ folderId: folderToDelete.id }).catch(() => null);
if (dynamicSecret) throw error;
const subfolder = await folderDAL.findOne({ parentId: folderToDelete.id }).catch(() => null);
if (subfolder) throw error;
}
const [doc] = await folderDAL.delete(
{
envId: env.id,
@@ -1315,7 +1332,7 @@ export const secretFolderServiceFactory = ({
});
}
await $checkFolderPolicy({ projectId, env, parentId: parentFolder.id, idOrName });
await $checkFolderPolicy({ projectId, env, parentId: parentFolder.id, idOrName, actor });
let folderToDelete = await folderDAL
.findOne({
@@ -37,6 +37,7 @@ export type TDeleteFolderDTO = {
environment: string;
path: string;
idOrName: string;
forceDelete?: boolean;
} & TProjectPermission;
export type TGetFolderDTO = {