Merge branch 'main' into feat/terraformCloudIntegration

This commit is contained in:
carlosmonastyrski
2025-04-11 16:37:05 -03:00
126 changed files with 5117 additions and 586 deletions
@@ -138,8 +138,8 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateHumanitecConnectionCredentialsSchema
| TValidatePostgresConnectionCredentialsSchema
| TValidateMsSqlConnectionCredentialsSchema
| TValidateTerraformCloudConnectionCredentialsSchema
| TValidateCamundaConnectionCredentialsSchema
| TValidateTerraformCloudConnectionCredentialsSchema
| TValidateVercelConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = {
@@ -141,6 +141,7 @@ export const projectRoleServiceFactory = ({
validateHandlebarTemplate("Project Role Update", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
});
const updatedRole = await projectRoleDAL.updateById(projectRole.id, {
...data,
permissions: data.permissions ? data.permissions : undefined
+49 -1
View File
@@ -1,12 +1,15 @@
import crypto from "crypto";
import { ProjectVersion, TProjects } from "@app/db/schemas";
import { createSshCaHelper } from "@app/ee/services/ssh/ssh-certificate-authority-fns";
import { SshCaKeySource } from "@app/ee/services/ssh/ssh-certificate-authority-types";
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
import { NotFoundError } from "@app/lib/errors";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { AddUserToWsDTO } from "./project-types";
import { AddUserToWsDTO, TBootstrapSshProjectDTO } from "./project-types";
export const assignWorkspaceKeysToMembers = ({ members, decryptKey, userPrivateKey }: AddUserToWsDTO) => {
const plaintextProjectKey = decryptAsymmetric({
@@ -102,3 +105,48 @@ export const getProjectKmsCertificateKeyId = async ({
return keyId;
};
/**
* Bootstraps an SSH project.
* - Creates a user and host SSH CA
* - Creates a project SSH config with the user and host SSH CA as defaults
*/
export const bootstrapSshProject = async ({
projectId,
sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL,
kmsService,
projectSshConfigDAL,
tx
}: TBootstrapSshProjectDTO) => {
const userSshCa = await createSshCaHelper({
projectId,
friendlyName: "User CA",
keyAlgorithm: SshCertKeyAlgorithm.ED25519,
keySource: SshCaKeySource.INTERNAL,
sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL,
kmsService,
tx
});
const hostSshCa = await createSshCaHelper({
projectId,
friendlyName: "Host CA",
keyAlgorithm: SshCertKeyAlgorithm.ED25519,
keySource: SshCaKeySource.INTERNAL,
sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL,
kmsService,
tx
});
await projectSshConfigDAL.create(
{
projectId,
defaultHostSshCaId: hostSshCa.id,
defaultUserSshCaId: userSshCa.id
},
tx
);
};
@@ -1,4 +1,4 @@
import { ForbiddenError } from "@casl/ability";
import { ForbiddenError, subject } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import {
@@ -15,13 +15,16 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import {
ProjectPermissionActions,
ProjectPermissionSecretActions,
ProjectPermissionSshHostActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types";
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
import { TSshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal";
import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal";
import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal";
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
@@ -61,8 +64,9 @@ import { TSlackIntegrationDALFactory } from "../slack/slack-integration-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { TUserDALFactory } from "../user/user-dal";
import { TProjectDALFactory } from "./project-dal";
import { assignWorkspaceKeysToMembers, createProjectKey } from "./project-fns";
import { assignWorkspaceKeysToMembers, bootstrapSshProject, createProjectKey } from "./project-fns";
import { TProjectQueueFactory } from "./project-queue";
import { TProjectSshConfigDALFactory } from "./project-ssh-config-dal";
import {
TCreateProjectDTO,
TDeleteProjectDTO,
@@ -77,6 +81,7 @@ import {
TListProjectSshCasDTO,
TListProjectSshCertificatesDTO,
TListProjectSshCertificateTemplatesDTO,
TListProjectSshHostsDTO,
TLoadProjectKmsBackupDTO,
TProjectAccessRequestDTO,
TSearchProjectsDTO,
@@ -97,8 +102,8 @@ export const DEFAULT_PROJECT_ENVS = [
];
type TProjectServiceFactoryDep = {
// TODO: Pick
projectDAL: TProjectDALFactory;
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "create">;
projectQueue: TProjectQueueFactory;
userDAL: TUserDALFactory;
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
@@ -123,9 +128,11 @@ type TProjectServiceFactoryDep = {
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getCertTemplatesByProjectId">;
pkiAlertDAL: Pick<TPkiAlertDALFactory, "find">;
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "find">;
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "find">;
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "find" | "create" | "transaction">;
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "create">;
sshCertificateDAL: Pick<TSshCertificateDALFactory, "find" | "countSshCertificatesInProject">;
sshCertificateTemplateDAL: Pick<TSshCertificateTemplateDALFactory, "find">;
sshHostDAL: Pick<TSshHostDALFactory, "find" | "findSshHostsWithLoginMappings">;
permissionService: TPermissionServiceFactory;
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -144,6 +151,7 @@ type TProjectServiceFactoryDep = {
| "getKmsById"
| "getProjectSecretManagerKmsKeyId"
| "deleteInternalKms"
| "createCipherPairWithDataKey"
>;
projectTemplateService: TProjectTemplateServiceFactory;
};
@@ -152,6 +160,7 @@ export type TProjectServiceFactory = ReturnType<typeof projectServiceFactory>;
export const projectServiceFactory = ({
projectDAL,
projectSshConfigDAL,
secretDAL,
secretV2BridgeDAL,
projectQueue,
@@ -177,8 +186,10 @@ export const projectServiceFactory = ({
pkiCollectionDAL,
pkiAlertDAL,
sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL,
sshCertificateDAL,
sshCertificateTemplateDAL,
sshHostDAL,
keyStore,
kmsService,
projectBotDAL,
@@ -266,6 +277,17 @@ export const projectServiceFactory = ({
tx
);
if (type === ProjectType.SSH) {
await bootstrapSshProject({
projectId: project.id,
sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL,
kmsService,
projectSshConfigDAL,
tx
});
}
// set ghost user as admin of project
const projectMembership = await projectMembershipDAL.create(
{
@@ -1046,6 +1068,48 @@ export const projectServiceFactory = ({
return cas;
};
/**
* Return list of SSH hosts for project
*/
const listProjectSshHosts = async ({
actorId,
actorOrgId,
actorAuthMethod,
actor,
projectId
}: TListProjectSshHostsDTO) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
const allowedHosts = [];
// (dangtony98): room to optimize
const hosts = await sshHostDAL.findSshHostsWithLoginMappings(projectId);
for (const host of hosts) {
try {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSshHostActions.Read,
subject(ProjectPermissionSub.SshHosts, {
hostname: host.hostname
})
);
allowedHosts.push(host);
} catch {
// intentionally ignore projects where user lacks access
}
}
return allowedHosts;
};
/**
* Return list of SSH certificates for project
*/
@@ -1443,6 +1507,7 @@ export const projectServiceFactory = ({
listProjectPkiCollections,
listProjectCertificateTemplates,
listProjectSshCas,
listProjectSshHosts,
listProjectSshCertificates,
listProjectSshCertificateTemplates,
updateVersionLimit,
@@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TProjectSshConfigDALFactory = ReturnType<typeof projectSshConfigDALFactory>;
export const projectSshConfigDALFactory = (db: TDbClient) => {
const projectSshConfigOrm = ormify(db, TableName.ProjectSshConfig);
return projectSshConfigOrm;
};
+15 -1
View File
@@ -1,6 +1,10 @@
import { Knex } from "knex";
import { ProjectType, SortDirection, TProjectKeys } from "@app/db/schemas";
import { ProjectType, TProjectKeys, SortDirection } from "@app/db/schemas";
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
import { TSshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal";
import { OrgServiceActor, TProjectPermission } from "@app/lib/types";
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
@@ -143,6 +147,7 @@ export type TGetProjectKmsKey = TProjectPermission;
export type TListProjectCertificateTemplatesDTO = TProjectPermission;
export type TListProjectSshCasDTO = TProjectPermission;
export type TListProjectSshHostsDTO = TProjectPermission;
export type TListProjectSshCertificateTemplatesDTO = TProjectPermission;
export type TListProjectSshCertificatesDTO = {
offset: number;
@@ -159,6 +164,15 @@ export type TUpdateProjectSlackConfig = {
secretRequestChannels: string;
} & TProjectPermission;
export type TBootstrapSshProjectDTO = {
projectId: string;
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "transaction" | "create">;
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "create">;
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "create">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
tx?: Knex;
};
export enum SearchProjectSortBy {
NAME = "name"
}
+6 -1
View File
@@ -87,7 +87,12 @@ View the complete details <${appCfg.SITE_URL}/secret-manager/${payload.projectId
The following permissions are requested: ${payload.permissions.join(", ")}
View the request and approve or deny it <${payload.approvalUrl}|here>.`;
View the request and approve or deny it <${payload.approvalUrl}|here>.${
payload.note
? `
User Note: ${payload.note}`
: ""
}`;
const payloadBlocks = [
{
@@ -76,5 +76,6 @@ export type TSlackNotification =
projectName: string;
permissions: string[];
approvalUrl: string;
note?: string;
};
};
@@ -40,6 +40,9 @@
{{/each}}
</ul>
</p>
{{#if note}}
<p>User Note: "{{note}}"</p>
{{/if}}
<p>
View the request and approve or deny it
@@ -18,6 +18,8 @@ export enum PostHogEventTypes {
SecretRequestDeleted = "Secret Request Deleted",
SignSshKey = "Sign SSH Key",
IssueSshCreds = "Issue SSH Credentials",
IssueSshHostUserCert = "Issue SSH Host User Certificate",
IssueSshHostHostCert = "Issue SSH Host Host Certificate",
SignCert = "Sign PKI Certificate",
IssueCert = "Issue PKI Certificate"
}
@@ -161,6 +163,26 @@ export type TIssueSshCredsEvent = {
};
};
export type TIssueSshHostUserCertEvent = {
event: PostHogEventTypes.IssueSshHostUserCert;
properties: {
sshHostId: string;
hostname: string;
principals: string[];
userAgent?: string;
};
};
export type TIssueSshHostHostCertEvent = {
event: PostHogEventTypes.IssueSshHostHostCert;
properties: {
sshHostId: string;
hostname: string;
principals: string[];
userAgent?: string;
};
};
export type TSignCertificateEvent = {
event: PostHogEventTypes.SignCert;
properties: {
@@ -195,6 +217,8 @@ export type TPostHogEvent = { distinctId: string } & (
| TSecretRequestDeletedEvent
| TSignSshKeyEvent
| TIssueSshCredsEvent
| TIssueSshHostUserCertEvent
| TIssueSshHostHostCertEvent
| TSignCertificateEvent
| TIssueCertificateEvent
);