mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-12 04:29:09 +00:00
Merge branch 'main' into ENG-3925
This commit is contained in:
@@ -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);
|
||||
@@ -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 () => {
|
||||
|
||||
Vendored
+8
@@ -518,6 +518,9 @@ import {
|
||||
TUsers,
|
||||
TUsersInsert,
|
||||
TUsersUpdate,
|
||||
TVaultExternalMigrationConfigs,
|
||||
TVaultExternalMigrationConfigsInsert,
|
||||
TVaultExternalMigrationConfigsUpdate,
|
||||
TWebhooks,
|
||||
TWebhooksInsert,
|
||||
TWebhooksUpdate,
|
||||
@@ -1345,5 +1348,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);
|
||||
}
|
||||
@@ -176,5 +176,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";
|
||||
|
||||
@@ -203,7 +203,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>
|
||||
>;
|
||||
@@ -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({
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -122,16 +122,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: {
|
||||
@@ -156,7 +156,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:
|
||||
@@ -175,13 +175,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.",
|
||||
@@ -195,25 +195,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."
|
||||
@@ -225,13 +225,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.",
|
||||
@@ -251,7 +251,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.",
|
||||
@@ -271,19 +271,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').",
|
||||
@@ -295,7 +295,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.",
|
||||
@@ -303,7 +303,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.",
|
||||
@@ -311,19 +311,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.",
|
||||
@@ -333,7 +333,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.",
|
||||
@@ -343,16 +343,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/.",
|
||||
@@ -361,7 +361,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:
|
||||
@@ -373,7 +373,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:
|
||||
@@ -385,21 +385,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.",
|
||||
@@ -409,7 +409,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.",
|
||||
@@ -419,19 +419,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:
|
||||
@@ -442,7 +442,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:
|
||||
@@ -453,19 +453,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:
|
||||
@@ -478,7 +478,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:
|
||||
@@ -491,19 +491,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:
|
||||
@@ -522,7 +522,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:
|
||||
@@ -541,41 +541,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: {
|
||||
@@ -589,10 +589,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.",
|
||||
@@ -606,7 +606,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.",
|
||||
@@ -620,19 +620,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.",
|
||||
@@ -649,7 +649,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.",
|
||||
@@ -666,10 +666,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;
|
||||
|
||||
@@ -853,12 +853,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.",
|
||||
@@ -871,11 +871,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.",
|
||||
@@ -949,7 +949,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;
|
||||
|
||||
@@ -1270,7 +1271,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.
|
||||
@@ -1296,7 +1297,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
|
||||
@@ -1322,17 +1323,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."
|
||||
}
|
||||
};
|
||||
@@ -1374,7 +1375,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.",
|
||||
@@ -1385,7 +1386,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.",
|
||||
@@ -1395,12 +1396,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: {
|
||||
@@ -1408,7 +1409,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."
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2355,6 +2356,9 @@ export const AppConnections = {
|
||||
sslRejectUnauthorized:
|
||||
"Whether or not to reject unauthorized SSL certificates (true/false). Set to false only in test environments with self-signed certificates.",
|
||||
sslCertificate: "The SSL certificate (PEM format) to use for secure connection."
|
||||
},
|
||||
LARAVEL_FORGE: {
|
||||
apiToken: "The API token used to authenticate with Laravel Forge."
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2507,6 +2511,14 @@ export const SecretSyncs = {
|
||||
branch: "The branch to sync preview secrets to.",
|
||||
teamId: "The ID of the Vercel team to sync secrets to."
|
||||
},
|
||||
LARAVEL_FORGE: {
|
||||
orgSlug: "The slug of the Laravel Forge org to sync secrets to.",
|
||||
orgName: "The name of the Laravel Forge org to sync secrets to.",
|
||||
serverId: "The ID of the Laravel Forge server to sync secrets to.",
|
||||
serverName: "The name of the Laravel Forge server to sync secrets to.",
|
||||
siteId: "The ID of the Laravel Forge site to sync secrets to.",
|
||||
siteName: "The name of the Laravel Forge site to sync secrets to."
|
||||
},
|
||||
WINDMILL: {
|
||||
workspace: "The Windmill workspace to sync secrets to.",
|
||||
path: "The Windmill workspace path to sync secrets to."
|
||||
|
||||
@@ -176,6 +176,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";
|
||||
@@ -535,6 +536,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);
|
||||
|
||||
@@ -1345,7 +1348,8 @@ export const registerRoutes = async (
|
||||
projectDAL,
|
||||
folderCommitService,
|
||||
secretApprovalPolicyService,
|
||||
secretV2BridgeDAL
|
||||
secretV2BridgeDAL,
|
||||
dynamicSecretDAL
|
||||
});
|
||||
|
||||
const secretImportService = secretImportServiceFactory({
|
||||
@@ -1882,13 +1886,6 @@ export const registerRoutes = async (
|
||||
notificationService
|
||||
});
|
||||
|
||||
const migrationService = externalMigrationServiceFactory({
|
||||
externalMigrationQueue,
|
||||
userDAL,
|
||||
permissionService,
|
||||
gatewayService
|
||||
});
|
||||
|
||||
const externalGroupOrgRoleMappingService = externalGroupOrgRoleMappingServiceFactory({
|
||||
permissionService,
|
||||
licenseService,
|
||||
@@ -2211,6 +2208,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();
|
||||
|
||||
|
||||
@@ -77,6 +77,10 @@ import {
|
||||
HumanitecConnectionListItemSchema,
|
||||
SanitizedHumanitecConnectionSchema
|
||||
} from "@app/services/app-connection/humanitec";
|
||||
import {
|
||||
LaravelForgeConnectionListItemSchema,
|
||||
SanitizedLaravelForgeConnectionSchema
|
||||
} from "@app/services/app-connection/laravel-forge";
|
||||
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
|
||||
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
||||
import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql";
|
||||
@@ -158,7 +162,8 @@ const SanitizedAppConnectionSchema = z.union([
|
||||
...SanitizedNetlifyConnectionSchema.options,
|
||||
...SanitizedOktaConnectionSchema.options,
|
||||
...SanitizedAzureADCSConnectionSchema.options,
|
||||
...SanitizedRedisConnectionSchema.options
|
||||
...SanitizedRedisConnectionSchema.options,
|
||||
...SanitizedLaravelForgeConnectionSchema.options
|
||||
]);
|
||||
|
||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||
@@ -200,7 +205,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||
NetlifyConnectionListItemSchema,
|
||||
OktaConnectionListItemSchema,
|
||||
AzureADCSConnectionListItemSchema,
|
||||
RedisConnectionListItemSchema
|
||||
RedisConnectionListItemSchema,
|
||||
LaravelForgeConnectionListItemSchema
|
||||
]);
|
||||
|
||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
@@ -24,6 +24,7 @@ import { registerGitLabConnectionRouter } from "./gitlab-connection-router";
|
||||
import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
|
||||
import { registerHerokuConnectionRouter } from "./heroku-connection-router";
|
||||
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
||||
import { registerLaravelForgeConnectionRouter } from "./laravel-forge-connection-router";
|
||||
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
||||
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
||||
import { registerMySqlConnectionRouter } from "./mysql-connection-router";
|
||||
@@ -71,6 +72,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
||||
[AppConnection.OnePass]: registerOnePassConnectionRouter,
|
||||
[AppConnection.Heroku]: registerHerokuConnectionRouter,
|
||||
[AppConnection.Render]: registerRenderConnectionRouter,
|
||||
[AppConnection.LaravelForge]: registerLaravelForgeConnectionRouter,
|
||||
[AppConnection.Flyio]: registerFlyioConnectionRouter,
|
||||
[AppConnection.GitLab]: registerGitLabConnectionRouter,
|
||||
[AppConnection.Cloudflare]: registerCloudflareConnectionRouter,
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
import z from "zod";
|
||||
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
CreateLaravelForgeConnectionSchema,
|
||||
SanitizedLaravelForgeConnectionSchema,
|
||||
UpdateLaravelForgeConnectionSchema
|
||||
} from "@app/services/app-connection/laravel-forge";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||
|
||||
export const registerLaravelForgeConnectionRouter = async (server: FastifyZodProvider) => {
|
||||
registerAppConnectionEndpoints({
|
||||
app: AppConnection.LaravelForge,
|
||||
server,
|
||||
sanitizedResponseSchema: SanitizedLaravelForgeConnectionSchema,
|
||||
createSchema: CreateLaravelForgeConnectionSchema,
|
||||
updateSchema: UpdateLaravelForgeConnectionSchema
|
||||
});
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: `/:connectionId/organizations`,
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
connectionId: z.string().uuid()
|
||||
}),
|
||||
response: {
|
||||
200: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
slug: z.string()
|
||||
})
|
||||
.array()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { connectionId } = req.params;
|
||||
const organizations = await server.services.appConnection.laravelForge.listOrganizations(
|
||||
connectionId,
|
||||
req.permission
|
||||
);
|
||||
|
||||
return organizations;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: `/:connectionId/servers`,
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
connectionId: z.string().uuid()
|
||||
}),
|
||||
querystring: z.object({
|
||||
organizationSlug: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
.array()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { connectionId } = req.params;
|
||||
const { organizationSlug } = req.query;
|
||||
const servers = await server.services.appConnection.laravelForge.listServers(
|
||||
connectionId,
|
||||
req.permission,
|
||||
organizationSlug
|
||||
);
|
||||
|
||||
return servers;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: `/:connectionId/sites`,
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
connectionId: z.string().uuid()
|
||||
}),
|
||||
querystring: z.object({
|
||||
organizationSlug: z.string(),
|
||||
serverId: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
.array()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { connectionId } = req.params;
|
||||
const { organizationSlug, serverId } = req.query;
|
||||
const sites = await server.services.appConnection.laravelForge.listSites(
|
||||
connectionId,
|
||||
req.permission,
|
||||
organizationSlug,
|
||||
serverId
|
||||
);
|
||||
|
||||
return sites;
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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: []
|
||||
|
||||
@@ -21,6 +21,7 @@ import { registerGitLabSyncRouter } from "./gitlab-sync-router";
|
||||
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
|
||||
import { registerHerokuSyncRouter } from "./heroku-sync-router";
|
||||
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
||||
import { registerLaravelForgeSyncRouter } from "./laravel-forge-sync-router";
|
||||
import { registerNetlifySyncRouter } from "./netlify-sync-router";
|
||||
import { registerRailwaySyncRouter } from "./railway-sync-router";
|
||||
import { registerRenderSyncRouter } from "./render-sync-router";
|
||||
@@ -63,5 +64,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
||||
[SecretSync.Checkly]: registerChecklySyncRouter,
|
||||
[SecretSync.DigitalOceanAppPlatform]: registerDigitalOceanAppPlatformSyncRouter,
|
||||
[SecretSync.Netlify]: registerNetlifySyncRouter,
|
||||
[SecretSync.Bitbucket]: registerBitbucketSyncRouter
|
||||
[SecretSync.Bitbucket]: registerBitbucketSyncRouter,
|
||||
[SecretSync.LaravelForge]: registerLaravelForgeSyncRouter
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
CreateLaravelForgeSyncSchema,
|
||||
LaravelForgeSyncSchema,
|
||||
UpdateLaravelForgeSyncSchema
|
||||
} from "@app/services/secret-sync/laravel-forge";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
|
||||
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||
|
||||
export const registerLaravelForgeSyncRouter = async (server: FastifyZodProvider) =>
|
||||
registerSyncSecretsEndpoints({
|
||||
destination: SecretSync.LaravelForge,
|
||||
server,
|
||||
responseSchema: LaravelForgeSyncSchema,
|
||||
createSchema: CreateLaravelForgeSyncSchema,
|
||||
updateSchema: UpdateLaravelForgeSyncSchema
|
||||
});
|
||||
@@ -44,6 +44,7 @@ import { GitLabSyncListItemSchema, GitLabSyncSchema } from "@app/services/secret
|
||||
import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault";
|
||||
import { HerokuSyncListItemSchema, HerokuSyncSchema } from "@app/services/secret-sync/heroku";
|
||||
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
||||
import { LaravelForgeSyncListItemSchema, LaravelForgeSyncSchema } from "@app/services/secret-sync/laravel-forge";
|
||||
import { NetlifySyncListItemSchema, NetlifySyncSchema } from "@app/services/secret-sync/netlify";
|
||||
import { RailwaySyncListItemSchema, RailwaySyncSchema } from "@app/services/secret-sync/railway/railway-sync-schemas";
|
||||
import { RenderSyncListItemSchema, RenderSyncSchema } from "@app/services/secret-sync/render/render-sync-schemas";
|
||||
@@ -84,7 +85,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
||||
ChecklySyncSchema,
|
||||
DigitalOceanAppPlatformSyncSchema,
|
||||
NetlifySyncSchema,
|
||||
BitbucketSyncSchema
|
||||
BitbucketSyncSchema,
|
||||
LaravelForgeSyncSchema
|
||||
]);
|
||||
|
||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||
@@ -117,7 +119,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||
ChecklySyncListItemSchema,
|
||||
SupabaseSyncListItemSchema,
|
||||
NetlifySyncListItemSchema,
|
||||
BitbucketSyncListItemSchema
|
||||
BitbucketSyncListItemSchema,
|
||||
LaravelForgeSyncListItemSchema
|
||||
]);
|
||||
|
||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -37,7 +37,8 @@ export enum AppConnection {
|
||||
DigitalOcean = "digital-ocean",
|
||||
Netlify = "netlify",
|
||||
Okta = "okta",
|
||||
Redis = "redis"
|
||||
Redis = "redis",
|
||||
LaravelForge = "laravel-forge"
|
||||
}
|
||||
|
||||
export enum AWSRegion {
|
||||
|
||||
@@ -103,6 +103,11 @@ import {
|
||||
HumanitecConnectionMethod,
|
||||
validateHumanitecConnectionCredentials
|
||||
} from "./humanitec";
|
||||
import {
|
||||
getLaravelForgeConnectionListItem,
|
||||
LaravelForgeConnectionMethod,
|
||||
validateLaravelForgeConnectionCredentials
|
||||
} from "./laravel-forge";
|
||||
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
|
||||
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
||||
import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums";
|
||||
@@ -187,6 +192,7 @@ export const listAppConnectionOptions = (projectType?: ProjectType) => {
|
||||
getOnePassConnectionListItem(),
|
||||
getHerokuConnectionListItem(),
|
||||
getRenderConnectionListItem(),
|
||||
getLaravelForgeConnectionListItem(),
|
||||
getFlyioConnectionListItem(),
|
||||
getGitLabConnectionListItem(),
|
||||
getCloudflareConnectionListItem(),
|
||||
@@ -316,6 +322,7 @@ export const validateAppConnectionCredentials = async (
|
||||
[AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Heroku]: validateHerokuConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Render]: validateRenderConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.LaravelForge]: validateLaravelForgeConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.GitLab]: validateGitLabConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
@@ -368,6 +375,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
||||
case ZabbixConnectionMethod.ApiToken:
|
||||
case DigitalOceanConnectionMethod.ApiToken:
|
||||
case OktaConnectionMethod.ApiToken:
|
||||
case LaravelForgeConnectionMethod.ApiToken:
|
||||
return "API Token";
|
||||
case PostgresConnectionMethod.UsernameAndPassword:
|
||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||
@@ -463,7 +471,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
||||
[AppConnection.DigitalOcean]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Netlify]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Okta]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Redis]: platformManagedCredentialsNotSupported
|
||||
[AppConnection.Redis]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.LaravelForge]: platformManagedCredentialsNotSupported
|
||||
};
|
||||
|
||||
export const enterpriseAppCheck = async (
|
||||
|
||||
@@ -28,6 +28,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
||||
[AppConnection.OnePass]: "1Password",
|
||||
[AppConnection.Heroku]: "Heroku",
|
||||
[AppConnection.Render]: "Render",
|
||||
[AppConnection.LaravelForge]: "Laravel Forge",
|
||||
[AppConnection.Flyio]: "Fly.io",
|
||||
[AppConnection.GitLab]: "GitLab",
|
||||
[AppConnection.Cloudflare]: "Cloudflare",
|
||||
@@ -70,6 +71,7 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
||||
[AppConnection.MySql]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Heroku]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Render]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.LaravelForge]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Flyio]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.GitLab]: AppConnectionPlanType.Regular,
|
||||
[AppConnection.Cloudflare]: AppConnectionPlanType.Regular,
|
||||
|
||||
@@ -89,6 +89,8 @@ import { ValidateHerokuConnectionCredentialsSchema } from "./heroku";
|
||||
import { herokuConnectionService } from "./heroku/heroku-connection-service";
|
||||
import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
||||
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
||||
import { ValidateLaravelForgeConnectionCredentialsSchema } from "./laravel-forge";
|
||||
import { laravelForgeConnectionService } from "./laravel-forge/laravel-forge-connection-service";
|
||||
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
||||
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||
import { ValidateMySqlConnectionCredentialsSchema } from "./mysql";
|
||||
@@ -157,6 +159,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
||||
[AppConnection.OnePass]: ValidateOnePassConnectionCredentialsSchema,
|
||||
[AppConnection.Heroku]: ValidateHerokuConnectionCredentialsSchema,
|
||||
[AppConnection.Render]: ValidateRenderConnectionCredentialsSchema,
|
||||
[AppConnection.LaravelForge]: ValidateLaravelForgeConnectionCredentialsSchema,
|
||||
[AppConnection.Flyio]: ValidateFlyioConnectionCredentialsSchema,
|
||||
[AppConnection.GitLab]: ValidateGitLabConnectionCredentialsSchema,
|
||||
[AppConnection.Cloudflare]: ValidateCloudflareConnectionCredentialsSchema,
|
||||
@@ -864,6 +867,7 @@ export const appConnectionServiceFactory = ({
|
||||
supabase: supabaseConnectionService(connectAppConnectionById),
|
||||
digitalOcean: digitalOceanAppPlatformConnectionService(connectAppConnectionById),
|
||||
netlify: netlifyConnectionService(connectAppConnectionById),
|
||||
okta: oktaConnectionService(connectAppConnectionById)
|
||||
okta: oktaConnectionService(connectAppConnectionById),
|
||||
laravelForge: laravelForgeConnectionService(connectAppConnectionById)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -148,6 +148,12 @@ import {
|
||||
THumanitecConnectionInput,
|
||||
TValidateHumanitecConnectionCredentialsSchema
|
||||
} from "./humanitec";
|
||||
import {
|
||||
TLaravelForgeConnection,
|
||||
TLaravelForgeConnectionConfig,
|
||||
TLaravelForgeConnectionInput,
|
||||
TValidateLaravelForgeConnectionCredentialsSchema
|
||||
} from "./laravel-forge";
|
||||
import {
|
||||
TLdapConnection,
|
||||
TLdapConnectionConfig,
|
||||
@@ -256,6 +262,7 @@ export type TAppConnection = { id: string } & (
|
||||
| TOnePassConnection
|
||||
| THerokuConnection
|
||||
| TRenderConnection
|
||||
| TLaravelForgeConnection
|
||||
| TFlyioConnection
|
||||
| TGitLabConnection
|
||||
| TCloudflareConnection
|
||||
@@ -302,6 +309,7 @@ export type TAppConnectionInput = { id: string } & (
|
||||
| TOnePassConnectionInput
|
||||
| THerokuConnectionInput
|
||||
| TRenderConnectionInput
|
||||
| TLaravelForgeConnectionInput
|
||||
| TFlyioConnectionInput
|
||||
| TGitLabConnectionInput
|
||||
| TCloudflareConnectionInput
|
||||
@@ -366,6 +374,7 @@ export type TAppConnectionConfig =
|
||||
| TOnePassConnectionConfig
|
||||
| THerokuConnectionConfig
|
||||
| TRenderConnectionConfig
|
||||
| TLaravelForgeConnectionConfig
|
||||
| TFlyioConnectionConfig
|
||||
| TGitLabConnectionConfig
|
||||
| TCloudflareConnectionConfig
|
||||
@@ -407,6 +416,7 @@ export type TValidateAppConnectionCredentialsSchema =
|
||||
| TValidateOnePassConnectionCredentialsSchema
|
||||
| TValidateHerokuConnectionCredentialsSchema
|
||||
| TValidateRenderConnectionCredentialsSchema
|
||||
| TValidateLaravelForgeConnectionCredentialsSchema
|
||||
| TValidateFlyioConnectionCredentialsSchema
|
||||
| TValidateGitLabConnectionCredentialsSchema
|
||||
| TValidateCloudflareConnectionCredentialsSchema
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./laravel-forge-connection-enums";
|
||||
export * from "./laravel-forge-connection-fns";
|
||||
export * from "./laravel-forge-connection-schemas";
|
||||
export * from "./laravel-forge-connection-types";
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum LaravelForgeConnectionMethod {
|
||||
ApiToken = "api-token"
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { LaravelForgeConnectionMethod } from "./laravel-forge-connection-enums";
|
||||
import {
|
||||
TLaravelForgeConnection,
|
||||
TLaravelForgeConnectionConfig,
|
||||
TLaravelForgeOrganization,
|
||||
TLaravelForgeServer,
|
||||
TLaravelForgeSite,
|
||||
TRawLaravelForgeOrganization,
|
||||
TRawLaravelForgeServer,
|
||||
TRawLaravelForgeSite
|
||||
} from "./laravel-forge-connection-types";
|
||||
|
||||
export const getLaravelForgeConnectionListItem = () => {
|
||||
return {
|
||||
name: "Laravel Forge" as const,
|
||||
app: AppConnection.LaravelForge as const,
|
||||
methods: Object.values(LaravelForgeConnectionMethod) as [LaravelForgeConnectionMethod.ApiToken]
|
||||
};
|
||||
};
|
||||
|
||||
export const validateLaravelForgeConnectionCredentials = async (config: TLaravelForgeConnectionConfig) => {
|
||||
const { credentials: inputCredentials } = config;
|
||||
|
||||
try {
|
||||
// Using the /api/me endpoint to validate the API token
|
||||
await request.get(`${IntegrationUrls.LARAVELFORGE_API_URL}/api/me`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${inputCredentials.apiToken}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw new BadRequestError({
|
||||
message: "Unable to validate connection: verify credentials"
|
||||
});
|
||||
}
|
||||
|
||||
return inputCredentials;
|
||||
};
|
||||
|
||||
type TLaravelForgeApiResponse<T> = {
|
||||
data: T[];
|
||||
links?: {
|
||||
next?: string;
|
||||
};
|
||||
meta?: {
|
||||
next_cursor?: string;
|
||||
prev_cursor?: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
const fetchAllPages = async <T>(
|
||||
apiToken: string,
|
||||
url: string,
|
||||
params?: Record<string, string | number>
|
||||
): Promise<T[]> => {
|
||||
const allItems: T[] = [];
|
||||
let nextUrl: string | null = url;
|
||||
const queryParams = params || {};
|
||||
|
||||
while (nextUrl) {
|
||||
try {
|
||||
const response: { data: TLaravelForgeApiResponse<T> } = await request.get<TLaravelForgeApiResponse<T>>(nextUrl, {
|
||||
params: queryParams,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
if (!response?.data?.data) {
|
||||
throw new InternalServerError({
|
||||
message: `Failed to fetch data from ${url}: Response was empty or malformed`
|
||||
});
|
||||
}
|
||||
|
||||
allItems.push(...response.data.data);
|
||||
|
||||
if (response.data.links?.next) {
|
||||
nextUrl = response.data.links.next;
|
||||
} else {
|
||||
nextUrl = null;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to fetch data from ${url}: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return allItems;
|
||||
};
|
||||
|
||||
export const listLaravelForgeOrganizations = async (
|
||||
appConnection: TLaravelForgeConnection
|
||||
): Promise<TLaravelForgeOrganization[]> => {
|
||||
const { credentials } = appConnection;
|
||||
const { apiToken } = credentials;
|
||||
|
||||
const rawOrganizations = await fetchAllPages<TRawLaravelForgeOrganization>(
|
||||
apiToken,
|
||||
`${IntegrationUrls.LARAVELFORGE_API_URL}/api/orgs`
|
||||
);
|
||||
|
||||
return rawOrganizations.map((org: TRawLaravelForgeOrganization) => ({
|
||||
id: org.id,
|
||||
name: org.attributes.name,
|
||||
slug: org.attributes.slug
|
||||
}));
|
||||
};
|
||||
|
||||
export const listLaravelForgeServers = async (
|
||||
appConnection: TLaravelForgeConnection,
|
||||
organizationSlug: string
|
||||
): Promise<TLaravelForgeServer[]> => {
|
||||
const { credentials } = appConnection;
|
||||
const { apiToken } = credentials;
|
||||
|
||||
const rawServers = await fetchAllPages<TRawLaravelForgeServer>(
|
||||
apiToken,
|
||||
`${IntegrationUrls.LARAVELFORGE_API_URL}/api/orgs/${organizationSlug}/servers`
|
||||
);
|
||||
|
||||
return rawServers.map((server: TRawLaravelForgeServer) => ({
|
||||
id: server.id,
|
||||
name: server.attributes.name
|
||||
}));
|
||||
};
|
||||
|
||||
export const listLaravelForgeSites = async (
|
||||
appConnection: TLaravelForgeConnection,
|
||||
organizationSlug: string,
|
||||
serverId: string
|
||||
): Promise<TLaravelForgeSite[]> => {
|
||||
const { credentials } = appConnection;
|
||||
const { apiToken } = credentials;
|
||||
|
||||
const rawSites = await fetchAllPages<TRawLaravelForgeSite>(
|
||||
apiToken,
|
||||
`${IntegrationUrls.LARAVELFORGE_API_URL}/api/orgs/${organizationSlug}/servers/${serverId}/sites`
|
||||
);
|
||||
|
||||
return rawSites.map((site: TRawLaravelForgeSite) => ({
|
||||
id: site.id,
|
||||
name: site.attributes.name
|
||||
}));
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import z from "zod";
|
||||
|
||||
import { AppConnections } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
BaseAppConnectionSchema,
|
||||
GenericCreateAppConnectionFieldsSchema,
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { LaravelForgeConnectionMethod } from "./laravel-forge-connection-enums";
|
||||
|
||||
export const LaravelForgeConnectionApiTokenCredentialsSchema = z.object({
|
||||
apiToken: z.string().trim().min(1, "API token required").describe(AppConnections.CREDENTIALS.LARAVEL_FORGE.apiToken)
|
||||
});
|
||||
|
||||
const BaseLaravelForgeConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.LaravelForge) });
|
||||
|
||||
export const LaravelForgeConnectionSchema = BaseLaravelForgeConnectionSchema.extend({
|
||||
method: z.literal(LaravelForgeConnectionMethod.ApiToken),
|
||||
credentials: LaravelForgeConnectionApiTokenCredentialsSchema
|
||||
});
|
||||
|
||||
export const SanitizedLaravelForgeConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseLaravelForgeConnectionSchema.extend({
|
||||
method: z.literal(LaravelForgeConnectionMethod.ApiToken),
|
||||
credentials: LaravelForgeConnectionApiTokenCredentialsSchema.pick({})
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateLaravelForgeConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z
|
||||
.literal(LaravelForgeConnectionMethod.ApiToken)
|
||||
.describe(AppConnections.CREATE(AppConnection.LaravelForge).method),
|
||||
credentials: LaravelForgeConnectionApiTokenCredentialsSchema.describe(
|
||||
AppConnections.CREATE(AppConnection.LaravelForge).credentials
|
||||
)
|
||||
})
|
||||
]);
|
||||
|
||||
export const CreateLaravelForgeConnectionSchema = ValidateLaravelForgeConnectionCredentialsSchema.and(
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.LaravelForge)
|
||||
);
|
||||
|
||||
export const UpdateLaravelForgeConnectionSchema = z
|
||||
.object({
|
||||
credentials: LaravelForgeConnectionApiTokenCredentialsSchema.optional().describe(
|
||||
AppConnections.UPDATE(AppConnection.LaravelForge).credentials
|
||||
)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.LaravelForge));
|
||||
|
||||
export const LaravelForgeConnectionListItemSchema = z.object({
|
||||
name: z.literal("Laravel Forge"),
|
||||
app: z.literal(AppConnection.LaravelForge),
|
||||
methods: z.nativeEnum(LaravelForgeConnectionMethod).array()
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import {
|
||||
listLaravelForgeOrganizations,
|
||||
listLaravelForgeServers,
|
||||
listLaravelForgeSites
|
||||
} from "./laravel-forge-connection-fns";
|
||||
import {
|
||||
TLaravelForgeConnection,
|
||||
TLaravelForgeOrganization,
|
||||
TLaravelForgeServer,
|
||||
TLaravelForgeSite
|
||||
} from "./laravel-forge-connection-types";
|
||||
|
||||
type TGetAppConnectionFunc = (
|
||||
app: AppConnection,
|
||||
connectionId: string,
|
||||
actor: OrgServiceActor
|
||||
) => Promise<TLaravelForgeConnection>;
|
||||
|
||||
export const laravelForgeConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||
const listOrganizations = async (
|
||||
connectionId: string,
|
||||
actor: OrgServiceActor
|
||||
): Promise<TLaravelForgeOrganization[]> => {
|
||||
const appConnection = await getAppConnection(AppConnection.LaravelForge, connectionId, actor);
|
||||
try {
|
||||
const organizations = await listLaravelForgeOrganizations(appConnection);
|
||||
return organizations;
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to list organizations for Laravel Forge connection");
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const listServers = async (
|
||||
connectionId: string,
|
||||
actor: OrgServiceActor,
|
||||
organizationSlug: string
|
||||
): Promise<TLaravelForgeServer[]> => {
|
||||
const appConnection = await getAppConnection(AppConnection.LaravelForge, connectionId, actor);
|
||||
try {
|
||||
const servers = await listLaravelForgeServers(appConnection, organizationSlug);
|
||||
return servers;
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to list servers for Laravel Forge connection");
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const listSites = async (
|
||||
connectionId: string,
|
||||
actor: OrgServiceActor,
|
||||
organizationSlug: string,
|
||||
serverId: string
|
||||
): Promise<TLaravelForgeSite[]> => {
|
||||
const appConnection = await getAppConnection(AppConnection.LaravelForge, connectionId, actor);
|
||||
try {
|
||||
const sites = await listLaravelForgeSites(appConnection, organizationSlug, serverId);
|
||||
return sites;
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to list sites for Laravel Forge connection");
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
listOrganizations,
|
||||
listServers,
|
||||
listSites
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import z from "zod";
|
||||
|
||||
import { DiscriminativePick } from "@app/lib/types";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import {
|
||||
CreateLaravelForgeConnectionSchema,
|
||||
LaravelForgeConnectionSchema,
|
||||
ValidateLaravelForgeConnectionCredentialsSchema
|
||||
} from "./laravel-forge-connection-schemas";
|
||||
|
||||
export type TLaravelForgeConnection = z.infer<typeof LaravelForgeConnectionSchema>;
|
||||
|
||||
export type TLaravelForgeConnectionInput = z.infer<typeof CreateLaravelForgeConnectionSchema> & {
|
||||
app: AppConnection.LaravelForge;
|
||||
};
|
||||
|
||||
export type TValidateLaravelForgeConnectionCredentialsSchema = typeof ValidateLaravelForgeConnectionCredentialsSchema;
|
||||
|
||||
export type TLaravelForgeConnectionConfig = DiscriminativePick<
|
||||
TLaravelForgeConnectionInput,
|
||||
"method" | "app" | "credentials"
|
||||
> & {
|
||||
orgSlug: string;
|
||||
};
|
||||
|
||||
export type TLaravelForgeOrganization = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type TLaravelForgeServer = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TLaravelForgeSite = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TRawLaravelForgeOrganization = {
|
||||
id: string;
|
||||
attributes: {
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TRawLaravelForgeServer = {
|
||||
id: string;
|
||||
attributes: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TRawLaravelForgeSite = {
|
||||
id: string;
|
||||
attributes: {
|
||||
name: 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 };
|
||||
};
|
||||
@@ -742,23 +742,27 @@ export const kmsServiceFactory = ({
|
||||
if (!project.kmsSecretManagerEncryptedDataKey) {
|
||||
const lock = await keyStore
|
||||
.acquireLock([KeyStorePrefixes.KmsProjectDataKeyCreation, projectId], 3000, { retryCount: 0 })
|
||||
.catch(() => null);
|
||||
.catch((err) => {
|
||||
logger.error(err, "KMS. Failed to acquire lock.");
|
||||
return null;
|
||||
});
|
||||
|
||||
try {
|
||||
if (!lock) {
|
||||
await keyStore.waitTillReady({
|
||||
key: `${KeyStorePrefixes.WaitUntilReadyKmsProjectDataKeyCreation}${projectId}`,
|
||||
keyCheckCb: (val) => val === "true",
|
||||
waitingCb: () => logger.debug("KMS. Waiting for secret manager data key to be created"),
|
||||
waitingCb: () => logger.info("KMS. Waiting for secret manager data key to be created"),
|
||||
delay: 500
|
||||
});
|
||||
|
||||
project = await projectDAL.findById(projectId, trx);
|
||||
} else {
|
||||
logger.info(`KMS. Generating KMS key for project ${projectId}`);
|
||||
const projectDataKey = await (trx || projectDAL).transaction(async (tx) => {
|
||||
project = await projectDAL.findById(projectId, tx);
|
||||
if (project.kmsSecretManagerEncryptedDataKey) {
|
||||
return;
|
||||
return project.kmsSecretManagerEncryptedDataKey;
|
||||
}
|
||||
|
||||
const dataKey = crypto.randomBytes(32);
|
||||
|
||||
@@ -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,
|
||||
@@ -534,13 +541,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 +651,8 @@ export const secretFolderServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
environment,
|
||||
path: secretPath,
|
||||
idOrName
|
||||
idOrName,
|
||||
forceDelete = false
|
||||
}: TDeleteFolderDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -664,7 +678,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 +704,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 +1345,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 = {
|
||||
|
||||
@@ -258,10 +258,6 @@ export const fnSecretsV2FromImports = async ({
|
||||
})[];
|
||||
}[] = [{ secretImports: rootSecretImports, depth: 0, parentImportedSecrets: [] }];
|
||||
|
||||
const processedSecretImports = await processReservedImports(rootSecretImports, secretImportDAL);
|
||||
|
||||
stack[0] = { secretImports: processedSecretImports, depth: 0, parentImportedSecrets: [] };
|
||||
|
||||
const processedImports: TSecretImportSecretsV2[] = [];
|
||||
|
||||
while (stack.length) {
|
||||
@@ -299,7 +295,9 @@ export const fnSecretsV2FromImports = async ({
|
||||
);
|
||||
const importedSecretsGroupByFolderId = groupBy(importedSecrets, (i) => i.folderId);
|
||||
|
||||
sanitizedImports.forEach(({ importPath, importEnv }) => {
|
||||
const processedBatchImports = await processReservedImports(sanitizedImports, secretImportDAL);
|
||||
|
||||
processedBatchImports.forEach(({ importPath, importEnv }) => {
|
||||
cyclicDetector.add(getImportUniqKey(importEnv.slug, importPath));
|
||||
});
|
||||
// now we need to check recursively deeper imports made inside other imports
|
||||
@@ -308,7 +306,7 @@ export const fnSecretsV2FromImports = async ({
|
||||
const deeperImportsGroupByFolderId = groupBy(deeperImports, (i) => i.folderId);
|
||||
|
||||
const isFirstIteration = !processedImports.length;
|
||||
sanitizedImports.forEach(({ importPath, importEnv, id, folderId }, i) => {
|
||||
processedBatchImports.forEach(({ importPath, importEnv, id, folderId }, i) => {
|
||||
const sourceImportFolder = importedFolderGroupBySourceImport[`${importEnv.id}-${importPath}`]?.[0];
|
||||
const secretsWithDuplicate = (importedSecretsGroupByFolderId?.[importedFolders?.[i]?.id as string] || [])
|
||||
.filter((item) =>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./laravel-forge-sync-constants";
|
||||
export * from "./laravel-forge-sync-fns";
|
||||
export * from "./laravel-forge-sync-schemas";
|
||||
export * from "./laravel-forge-sync-types";
|
||||
@@ -0,0 +1,10 @@
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
export const LARAVEL_FORGE_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||
name: "Laravel Forge",
|
||||
destination: SecretSync.LaravelForge,
|
||||
connection: AppConnection.LaravelForge,
|
||||
canImportSecrets: true
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import {
|
||||
LaravelForgeSecret,
|
||||
TGetLaravelForgeSecrets,
|
||||
TLaravelForgeSecrets,
|
||||
TLaravelForgeSyncWithCredentials
|
||||
} from "./laravel-forge-sync-types";
|
||||
|
||||
const getLaravelForgeSecretsRaw = async ({ apiToken, orgSlug, serverId, siteId }: TGetLaravelForgeSecrets) => {
|
||||
const { data } = await request.get<TLaravelForgeSecrets>(
|
||||
`${IntegrationUrls.LARAVELFORGE_API_URL}/api/orgs/${orgSlug}/servers/${serverId}/sites/${siteId}/environment`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return data.data.attributes.content;
|
||||
};
|
||||
|
||||
const parseEnv = (str: string) => {
|
||||
const lines = str.split("\n");
|
||||
const parsed: { key: string; value: string }[] = [];
|
||||
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
const trimmed = lines[i].trim();
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (trimmed === "" || trimmed.startsWith("#")) {
|
||||
i += 1;
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.includes("=")) {
|
||||
const equalIndex = trimmed.indexOf("=");
|
||||
const key = trimmed.substring(0, equalIndex).trim();
|
||||
const valueRaw = trimmed.substring(equalIndex + 1).trim();
|
||||
|
||||
// Check if value starts with a quote
|
||||
const startsWithDoubleQuote = valueRaw.startsWith('"');
|
||||
const startsWithSingleQuote = valueRaw.startsWith("'");
|
||||
|
||||
if (startsWithDoubleQuote || startsWithSingleQuote) {
|
||||
const quoteChar = startsWithDoubleQuote ? '"' : "'";
|
||||
|
||||
const closingQuoteIndex = valueRaw.indexOf(quoteChar, 1);
|
||||
|
||||
if (closingQuoteIndex !== -1) {
|
||||
// Single-line quoted value
|
||||
const value = valueRaw.slice(1, closingQuoteIndex);
|
||||
parsed.push({ key, value });
|
||||
i += 1;
|
||||
} else {
|
||||
// Multiline quoted value - collect lines until closing quote
|
||||
let value = valueRaw.slice(1);
|
||||
i += 1;
|
||||
|
||||
while (i < lines.length) {
|
||||
const nextLine = lines[i];
|
||||
const closingIndex = nextLine.indexOf(quoteChar);
|
||||
|
||||
if (closingIndex !== -1) {
|
||||
value += `\n${nextLine.substring(0, closingIndex)}`;
|
||||
parsed.push({ key, value });
|
||||
i += 1;
|
||||
break;
|
||||
} else {
|
||||
value += `\n${nextLine}`;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Unquoted value
|
||||
parsed.push({ key, value: valueRaw });
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const getLaravelForgeSecrets = async (secretSync: TLaravelForgeSyncWithCredentials): Promise<LaravelForgeSecret[]> => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { orgSlug, serverId, siteId }
|
||||
} = secretSync;
|
||||
|
||||
const { apiToken } = connection.credentials;
|
||||
|
||||
const secrets = await getLaravelForgeSecretsRaw({ apiToken, orgSlug, serverId, siteId });
|
||||
|
||||
const parsedSecrets = parseEnv(secrets);
|
||||
|
||||
return parsedSecrets;
|
||||
};
|
||||
|
||||
const buildEnvString = (secrets: LaravelForgeSecret[]) => {
|
||||
if (secrets.length === 0) {
|
||||
return "# .env";
|
||||
}
|
||||
|
||||
return secrets
|
||||
.map((secret) => {
|
||||
const { value } = secret;
|
||||
|
||||
if (value.includes(`"`)) {
|
||||
return `${secret.key}='${value}'`;
|
||||
}
|
||||
|
||||
if (value.includes(" ") || value.includes("\n") || value.includes(`'`)) {
|
||||
return `${secret.key}="${value}"`;
|
||||
}
|
||||
return `${secret.key}=${value}`;
|
||||
})
|
||||
.join("\n");
|
||||
};
|
||||
|
||||
const updateLaravelForgeSecrets = async (secretSync: TLaravelForgeSyncWithCredentials, envString: string) => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { orgSlug, serverId, siteId }
|
||||
} = secretSync;
|
||||
|
||||
const { apiToken } = connection.credentials;
|
||||
|
||||
await request.put(
|
||||
`${IntegrationUrls.LARAVELFORGE_API_URL}/api/orgs/${orgSlug}/servers/${serverId}/sites/${siteId}/environment`,
|
||||
{
|
||||
environment: envString
|
||||
},
|
||||
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const LaravelForgeSyncFns = {
|
||||
async syncSecrets(secretSync: TLaravelForgeSyncWithCredentials, secretMap: TSecretMap) {
|
||||
const {
|
||||
environment,
|
||||
syncOptions: { disableSecretDeletion, keySchema }
|
||||
} = secretSync;
|
||||
|
||||
const secrets = await getLaravelForgeSecrets(secretSync);
|
||||
|
||||
// Create a map of the existing secrets
|
||||
const updatedSecretsMap = new Map(secrets.map((secret) => [secret.key, secret.value]));
|
||||
|
||||
for (const [key, { value }] of Object.entries(secretMap)) {
|
||||
// Add the new secrets to the map
|
||||
updatedSecretsMap.set(key, value);
|
||||
}
|
||||
|
||||
if (!disableSecretDeletion) {
|
||||
secrets.forEach((secret) => {
|
||||
if (!matchesSchema(secret.key, environment?.slug || "", keySchema)) return;
|
||||
|
||||
if (!secretMap[secret.key]) {
|
||||
updatedSecretsMap.delete(secret.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const updatedSecrets = Array.from(updatedSecretsMap.entries()).map(([key, value]) => ({ key, value }));
|
||||
|
||||
const envString = buildEnvString(updatedSecrets);
|
||||
|
||||
await updateLaravelForgeSecrets(secretSync, envString);
|
||||
},
|
||||
|
||||
async getSecrets(secretSync: TLaravelForgeSyncWithCredentials): Promise<TSecretMap> {
|
||||
const secrets = await getLaravelForgeSecrets(secretSync);
|
||||
return Object.fromEntries(secrets.map((secret) => [secret.key, { value: secret.value }]));
|
||||
},
|
||||
|
||||
async removeSecrets(secretSync: TLaravelForgeSyncWithCredentials, secretMap: TSecretMap) {
|
||||
const existingSecrets = await getLaravelForgeSecrets(secretSync);
|
||||
|
||||
const newSecrets = existingSecrets.filter((secret) => !Object.hasOwn(secretMap, secret.key));
|
||||
|
||||
if (newSecrets.length === existingSecrets.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const envString = buildEnvString(newSecrets);
|
||||
|
||||
await updateLaravelForgeSecrets(secretSync, envString);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretSyncs } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
import {
|
||||
BaseSecretSyncSchema,
|
||||
GenericCreateSecretSyncFieldsSchema,
|
||||
GenericUpdateSecretSyncFieldsSchema
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
const slugValidator = (val: string) => {
|
||||
return new RE2("^[a-z0-9.-]+$").test(val) && !new RE2(".[-]$").test(val);
|
||||
};
|
||||
|
||||
const LaravelForgeSyncDestinationConfigSchema = z.object({
|
||||
orgSlug: z
|
||||
.string()
|
||||
.min(1, "Org Slug is required")
|
||||
.max(512, "Org Slug cannot exceed 512 characters")
|
||||
.refine(
|
||||
(val) => slugValidator(val),
|
||||
"Org Slug can only contain lowercase letters, numbers, dots, and dashes, and cannot end with a dot or dash."
|
||||
)
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.orgSlug),
|
||||
orgName: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.orgName),
|
||||
serverId: z
|
||||
.string()
|
||||
.min(1, "Server ID is required")
|
||||
.refine((val) => !Number.isNaN(Number(val)), "Server ID must be a valid integer")
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.serverId),
|
||||
serverName: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.serverName),
|
||||
siteId: z.string().min(1, "Site ID is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.siteId),
|
||||
siteName: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.siteName)
|
||||
});
|
||||
|
||||
const LaravelForgeSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const LaravelForgeSyncSchema = BaseSecretSyncSchema(
|
||||
SecretSync.LaravelForge,
|
||||
LaravelForgeSyncOptionsConfig
|
||||
).extend({
|
||||
destination: z.literal(SecretSync.LaravelForge),
|
||||
destinationConfig: LaravelForgeSyncDestinationConfigSchema
|
||||
});
|
||||
|
||||
export const CreateLaravelForgeSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.LaravelForge,
|
||||
LaravelForgeSyncOptionsConfig
|
||||
).extend({
|
||||
destinationConfig: LaravelForgeSyncDestinationConfigSchema
|
||||
});
|
||||
|
||||
export const UpdateLaravelForgeSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
SecretSync.LaravelForge,
|
||||
LaravelForgeSyncOptionsConfig
|
||||
).extend({
|
||||
destinationConfig: LaravelForgeSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const LaravelForgeSyncListItemSchema = z.object({
|
||||
name: z.literal("Laravel Forge"),
|
||||
connection: z.literal(AppConnection.LaravelForge),
|
||||
destination: z.literal(SecretSync.LaravelForge),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import z from "zod";
|
||||
|
||||
import { TLaravelForgeConnection } from "@app/services/app-connection/laravel-forge";
|
||||
|
||||
import {
|
||||
CreateLaravelForgeSyncSchema,
|
||||
LaravelForgeSyncListItemSchema,
|
||||
LaravelForgeSyncSchema
|
||||
} from "./laravel-forge-sync-schemas";
|
||||
|
||||
export type TLaravelForgeSyncListItem = z.infer<typeof LaravelForgeSyncListItemSchema>;
|
||||
|
||||
export type TLaravelForgeSync = z.infer<typeof LaravelForgeSyncSchema>;
|
||||
|
||||
export type TLaravelForgeSyncInput = z.infer<typeof CreateLaravelForgeSyncSchema>;
|
||||
|
||||
export type TLaravelForgeSyncWithCredentials = TLaravelForgeSync & {
|
||||
connection: TLaravelForgeConnection;
|
||||
};
|
||||
|
||||
export type TGetLaravelForgeSecrets = {
|
||||
apiToken: string;
|
||||
orgSlug: string;
|
||||
serverId: string;
|
||||
siteId: string;
|
||||
};
|
||||
|
||||
export type TLaravelForgeSecrets = {
|
||||
data: {
|
||||
id: string;
|
||||
type: string;
|
||||
attributes: {
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type LaravelForgeSecret = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
@@ -28,7 +28,8 @@ export enum SecretSync {
|
||||
Checkly = "checkly",
|
||||
DigitalOceanAppPlatform = "digital-ocean-app-platform",
|
||||
Netlify = "netlify",
|
||||
Bitbucket = "bitbucket"
|
||||
Bitbucket = "bitbucket",
|
||||
LaravelForge = "laravel-forge"
|
||||
}
|
||||
|
||||
export enum SecretSyncInitialSyncBehavior {
|
||||
|
||||
@@ -49,6 +49,8 @@ import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault";
|
||||
import { HEROKU_SYNC_LIST_OPTION, HerokuSyncFns } from "./heroku";
|
||||
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
|
||||
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
|
||||
import { LARAVEL_FORGE_SYNC_LIST_OPTION } from "./laravel-forge";
|
||||
import { LaravelForgeSyncFns } from "./laravel-forge/laravel-forge-sync-fns";
|
||||
import { NETLIFY_SYNC_LIST_OPTION, NetlifySyncFns } from "./netlify";
|
||||
import { RAILWAY_SYNC_LIST_OPTION } from "./railway/railway-sync-constants";
|
||||
import { RailwaySyncFns } from "./railway/railway-sync-fns";
|
||||
@@ -91,7 +93,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
||||
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION,
|
||||
[SecretSync.DigitalOceanAppPlatform]: DIGITAL_OCEAN_APP_PLATFORM_SYNC_LIST_OPTION,
|
||||
[SecretSync.Netlify]: NETLIFY_SYNC_LIST_OPTION,
|
||||
[SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION
|
||||
[SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION,
|
||||
[SecretSync.LaravelForge]: LARAVEL_FORGE_SYNC_LIST_OPTION
|
||||
};
|
||||
|
||||
export const listSecretSyncOptions = () => {
|
||||
@@ -277,6 +280,8 @@ export const SecretSyncFns = {
|
||||
return NetlifySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Bitbucket:
|
||||
return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.LaravelForge:
|
||||
return LaravelForgeSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||
@@ -393,6 +398,9 @@ export const SecretSyncFns = {
|
||||
case SecretSync.Bitbucket:
|
||||
secretMap = await BitbucketSyncFns.getSecrets(secretSync);
|
||||
break;
|
||||
case SecretSync.LaravelForge:
|
||||
secretMap = await LaravelForgeSyncFns.getSecrets(secretSync);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||
@@ -486,6 +494,8 @@ export const SecretSyncFns = {
|
||||
return NetlifySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Bitbucket:
|
||||
return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.LaravelForge:
|
||||
return LaravelForgeSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||
|
||||
@@ -32,7 +32,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
||||
[SecretSync.Checkly]: "Checkly",
|
||||
[SecretSync.DigitalOceanAppPlatform]: "Digital Ocean App Platform",
|
||||
[SecretSync.Netlify]: "Netlify",
|
||||
[SecretSync.Bitbucket]: "Bitbucket"
|
||||
[SecretSync.Bitbucket]: "Bitbucket",
|
||||
[SecretSync.LaravelForge]: "Laravel Forge"
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
@@ -65,7 +66,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
[SecretSync.Checkly]: AppConnection.Checkly,
|
||||
[SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean,
|
||||
[SecretSync.Netlify]: AppConnection.Netlify,
|
||||
[SecretSync.Bitbucket]: AppConnection.Bitbucket
|
||||
[SecretSync.Bitbucket]: AppConnection.Bitbucket,
|
||||
[SecretSync.LaravelForge]: AppConnection.LaravelForge
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
||||
@@ -98,7 +100,8 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
||||
[SecretSync.Checkly]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.DigitalOceanAppPlatform]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.Netlify]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular
|
||||
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.LaravelForge]: SecretSyncPlanType.Regular
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_SKIP_FIELDS_MAP: Record<SecretSync, string[]> = {
|
||||
@@ -140,7 +143,8 @@ export const SECRET_SYNC_SKIP_FIELDS_MAP: Record<SecretSync, string[]> = {
|
||||
[SecretSync.Checkly]: ["groupName", "accountName"],
|
||||
[SecretSync.DigitalOceanAppPlatform]: ["appName"],
|
||||
[SecretSync.Netlify]: ["accountName", "siteName"],
|
||||
[SecretSync.Bitbucket]: []
|
||||
[SecretSync.Bitbucket]: [],
|
||||
[SecretSync.LaravelForge]: []
|
||||
};
|
||||
|
||||
const defaultDuplicateCheck: DestinationDuplicateCheckFn = () => true;
|
||||
@@ -199,5 +203,6 @@ export const DESTINATION_DUPLICATE_CHECK_MAP: Record<SecretSync, DestinationDupl
|
||||
[SecretSync.Checkly]: defaultDuplicateCheck,
|
||||
[SecretSync.DigitalOceanAppPlatform]: defaultDuplicateCheck,
|
||||
[SecretSync.Netlify]: defaultDuplicateCheck,
|
||||
[SecretSync.Bitbucket]: defaultDuplicateCheck
|
||||
[SecretSync.Bitbucket]: defaultDuplicateCheck,
|
||||
[SecretSync.LaravelForge]: defaultDuplicateCheck
|
||||
};
|
||||
|
||||
@@ -117,6 +117,12 @@ import {
|
||||
THumanitecSyncListItem,
|
||||
THumanitecSyncWithCredentials
|
||||
} from "./humanitec";
|
||||
import {
|
||||
TLaravelForgeSync,
|
||||
TLaravelForgeSyncInput,
|
||||
TLaravelForgeSyncListItem,
|
||||
TLaravelForgeSyncWithCredentials
|
||||
} from "./laravel-forge";
|
||||
import { TNetlifySync, TNetlifySyncInput, TNetlifySyncListItem, TNetlifySyncWithCredentials } from "./netlify";
|
||||
import {
|
||||
TRailwaySync,
|
||||
@@ -164,6 +170,7 @@ export type TSecretSync =
|
||||
| TTerraformCloudSync
|
||||
| TCamundaSync
|
||||
| TVercelSync
|
||||
| TLaravelForgeSync
|
||||
| TWindmillSync
|
||||
| THCVaultSync
|
||||
| TTeamCitySync
|
||||
@@ -212,7 +219,8 @@ export type TSecretSyncWithCredentials =
|
||||
| TSupabaseSyncWithCredentials
|
||||
| TDigitalOceanAppPlatformSyncWithCredentials
|
||||
| TNetlifySyncWithCredentials
|
||||
| TBitbucketSyncWithCredentials;
|
||||
| TBitbucketSyncWithCredentials
|
||||
| TLaravelForgeSyncWithCredentials;
|
||||
|
||||
export type TSecretSyncInput =
|
||||
| TAwsParameterStoreSyncInput
|
||||
@@ -244,7 +252,8 @@ export type TSecretSyncInput =
|
||||
| TSupabaseSyncInput
|
||||
| TDigitalOceanAppPlatformSyncInput
|
||||
| TNetlifySyncInput
|
||||
| TBitbucketSyncInput;
|
||||
| TBitbucketSyncInput
|
||||
| TLaravelForgeSyncInput;
|
||||
|
||||
export type TSecretSyncListItem =
|
||||
| TAwsParameterStoreSyncListItem
|
||||
@@ -259,6 +268,7 @@ export type TSecretSyncListItem =
|
||||
| TTerraformCloudSyncListItem
|
||||
| TCamundaSyncListItem
|
||||
| TVercelSyncListItem
|
||||
| TLaravelForgeSyncListItem
|
||||
| TWindmillSyncListItem
|
||||
| THCVaultSyncListItem
|
||||
| TTeamCitySyncListItem
|
||||
|
||||
Reference in New Issue
Block a user