Move ssh back to project level

This commit is contained in:
Tuan Dang
2024-12-09 21:36:42 -08:00
parent 42249726d4
commit 7cf297344b
61 changed files with 837 additions and 635 deletions
+8
View File
@@ -317,6 +317,9 @@ import {
TSshCertificateAuthoritySecrets,
TSshCertificateAuthoritySecretsInsert,
TSshCertificateAuthoritySecretsUpdate,
TSshCertificateBodies,
TSshCertificateBodiesInsert,
TSshCertificateBodiesUpdate,
TSshCertificates,
TSshCertificatesInsert,
TSshCertificatesUpdate,
@@ -404,6 +407,11 @@ declare module "knex/types/tables" {
TSshCertificatesInsert,
TSshCertificatesUpdate
>;
[TableName.SshCertificateBody]: KnexOriginal.CompositeTableType<
TSshCertificateBodies,
TSshCertificateBodiesInsert,
TSshCertificateBodiesUpdate
>;
[TableName.CertificateAuthority]: KnexOriginal.CompositeTableType<
TCertificateAuthorities,
TCertificateAuthoritiesInsert,
@@ -8,8 +8,8 @@ export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable(TableName.SshCertificateAuthority, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.string("status").notNullable(); // active / disabled
t.string("friendlyName").notNullable();
t.string("keyAlgorithm").notNullable();
@@ -60,7 +60,6 @@ export async function up(knex: Knex): Promise<void> {
.onDelete("SET NULL");
t.string("serialNumber").notNullable().unique();
t.string("certType").notNullable(); // user or host
t.text("publicKey").notNullable(); // public key in OpenSSH format
t.specificType("principals", "text[]").notNullable();
t.string("keyId").notNullable();
t.datetime("notBefore").notNullable();
@@ -68,9 +67,24 @@ export async function up(knex: Knex): Promise<void> {
});
await createOnUpdateTrigger(knex, TableName.SshCertificate);
}
if (!(await knex.schema.hasTable(TableName.SshCertificateBody))) {
await knex.schema.createTable(TableName.SshCertificateBody, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true);
t.uuid("sshCertId").notNullable().unique();
t.foreign("sshCertId").references("id").inTable(TableName.SshCertificate).onDelete("CASCADE");
t.binary("encryptedCertificate").notNullable();
});
await createOnUpdateTrigger(knex, TableName.SshCertificateBody);
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SshCertificateBody);
await dropOnUpdateTrigger(knex, TableName.SshCertificateBody);
await knex.schema.dropTableIfExists(TableName.SshCertificate);
await dropOnUpdateTrigger(knex, TableName.SshCertificate);
+1
View File
@@ -107,6 +107,7 @@ export * from "./service-tokens";
export * from "./slack-integrations";
export * from "./ssh-certificate-authorities";
export * from "./ssh-certificate-authority-secrets";
export * from "./ssh-certificate-bodies";
export * from "./ssh-certificate-templates";
export * from "./ssh-certificates";
export * from "./super-admin";
+1
View File
@@ -6,6 +6,7 @@ export enum TableName {
SshCertificateAuthoritySecret = "ssh_certificate_authority_secrets",
SshCertificateTemplate = "ssh_certificate_templates",
SshCertificate = "ssh_certificates",
SshCertificateBody = "ssh_certificate_bodies",
CertificateAuthority = "certificate_authorities",
CertificateTemplateEstConfig = "certificate_template_est_configs",
CertificateAuthorityCert = "certificate_authority_certs",
@@ -11,7 +11,7 @@ export const SshCertificateAuthoritiesSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
orgId: z.string().uuid(),
projectId: z.string(),
status: z.string(),
friendlyName: z.string(),
keyAlgorithm: z.string()
@@ -0,0 +1,22 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const SshCertificateBodiesSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
sshCertId: z.string().uuid(),
encryptedCertificate: zodBuffer
});
export type TSshCertificateBodies = z.infer<typeof SshCertificateBodiesSchema>;
export type TSshCertificateBodiesInsert = Omit<z.input<typeof SshCertificateBodiesSchema>, TImmutableDBKeys>;
export type TSshCertificateBodiesUpdate = Partial<Omit<z.input<typeof SshCertificateBodiesSchema>, TImmutableDBKeys>>;
@@ -15,7 +15,6 @@ export const SshCertificatesSchema = z.object({
sshCertificateTemplateId: z.string().uuid().nullable().optional(),
serialNumber: z.string(),
certType: z.string(),
publicKey: z.string(),
principals: z.string().array(),
keyId: z.string(),
notBefore: z.date(),
@@ -21,6 +21,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
schema: {
description: "Create SSH CA",
body: z.object({
projectId: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.projectId),
friendlyName: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.friendlyName),
keyAlgorithm: z
.nativeEnum(CertKeyAlgorithm)
@@ -46,7 +47,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: ca.orgId,
projectId: ca.projectId,
event: {
type: EventType.CREATE_SSH_CA,
metadata: {
@@ -93,7 +94,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: ca.orgId,
projectId: ca.projectId,
event: {
type: EventType.GET_SSH_CA,
metadata: {
@@ -169,7 +170,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: ca.orgId,
projectId: ca.projectId,
event: {
type: EventType.UPDATE_SSH_CA,
metadata: {
@@ -215,7 +216,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: ca.orgId,
projectId: ca.projectId,
event: {
type: EventType.DELETE_SSH_CA,
metadata: {
@@ -260,7 +261,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: ca.orgId,
projectId: ca.projectId,
event: {
type: EventType.GET_SSH_CA_CERTIFICATE_TEMPLATES,
metadata: {
@@ -41,7 +41,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: certificateTemplate.orgId,
projectId: certificateTemplate.projectId,
event: {
type: EventType.GET_SSH_CERTIFICATE_TEMPLATE,
metadata: {
@@ -107,7 +107,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: ca.orgId,
projectId: ca.projectId,
event: {
type: EventType.CREATE_SSH_CERTIFICATE_TEMPLATE,
metadata: {
@@ -178,7 +178,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificateTemplate, orgId } = await server.services.sshCertificateTemplate.updateSshCertTemplate({
const { certificateTemplate, projectId } = await server.services.sshCertificateTemplate.updateSshCertTemplate({
...req.body,
id: req.params.certificateTemplateId,
actor: req.permission.type,
@@ -189,7 +189,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId,
projectId,
event: {
type: EventType.UPDATE_SSH_CERTIFICATE_TEMPLATE,
metadata: {
@@ -238,7 +238,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: certificateTemplate.orgId,
projectId: certificateTemplate.projectId,
event: {
type: EventType.DELETE_SSH_CERTIFICATE_TEMPLATE,
metadata: {
+2
View File
@@ -20,6 +20,7 @@ export const registerSshRouter = async (server: FastifyZodProvider) => {
schema: {
description: "Sign SSH public key",
body: z.object({
projectId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.projectId),
templateName: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.templateName),
publicKey: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.publicKey),
certType: z
@@ -86,6 +87,7 @@ export const registerSshRouter = async (server: FastifyZodProvider) => {
schema: {
description: "Issue SSH credentials (certificate + key)",
body: z.object({
projectId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.projectId),
templateName: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.templateName),
keyAlgorithm: z
.nativeEnum(CertKeyAlgorithm)
@@ -27,10 +27,7 @@ export enum OrgPermissionSubjects {
Kms = "kms",
AdminConsole = "organization-admin-console",
AuditLogs = "audit-logs",
ProjectTemplates = "project-templates",
SshCertificates = "ssh-certificates",
SshCertificateAuthorities = "ssh-certificate-authorities",
SshCertificateTemplates = "ssh-certificate-templates"
ProjectTemplates = "project-templates"
}
export type OrgPermissionSet =
@@ -49,10 +46,7 @@ export type OrgPermissionSet =
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificateAuthorities]
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificates]
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificateTemplates];
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
const buildAdminPermission = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
@@ -129,19 +123,6 @@ const buildAdminPermission = () => {
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificates);
can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateAuthorities);
can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificateAuthorities);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SshCertificateAuthorities);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SshCertificateAuthorities);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateTemplates);
can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificateTemplates);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SshCertificateTemplates);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SshCertificateTemplates);
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
return rules;
@@ -172,11 +153,6 @@ const buildMemberPermission = () => {
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateAuthorities);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificates);
can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateTemplates);
return rules;
};
@@ -54,6 +54,9 @@ export enum ProjectPermissionSub {
CertificateAuthorities = "certificate-authorities",
Certificates = "certificates",
CertificateTemplates = "certificate-templates",
SshCertificateAuthorities = "ssh-certificate-authorities",
SshCertificates = "ssh-certificates",
SshCertificateTemplates = "ssh-certificate-templates",
PkiAlerts = "pki-alerts",
PkiCollections = "pki-collections",
Kms = "kms",
@@ -125,6 +128,9 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
@@ -322,6 +328,28 @@ const GeneralPermissionSchema = [
"Describe what action an entity can take."
)
}),
z.object({
subject: z
.literal(ProjectPermissionSub.SshCertificateAuthorities)
.describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(ProjectPermissionSub.SshCertificates).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z
.literal(ProjectPermissionSub.SshCertificateTemplates)
.describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(ProjectPermissionSub.PkiAlerts).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
@@ -448,7 +476,10 @@ const buildAdminPermissionRules = () => {
ProjectPermissionSub.Certificates,
ProjectPermissionSub.CertificateTemplates,
ProjectPermissionSub.PkiAlerts,
ProjectPermissionSub.PkiCollections
ProjectPermissionSub.PkiCollections,
ProjectPermissionSub.SshCertificateAuthorities,
ProjectPermissionSub.SshCertificates,
ProjectPermissionSub.SshCertificateTemplates
].forEach((el) => {
can(
[
@@ -633,6 +664,11 @@ const buildMemberPermissionRules = () => {
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateAuthorities);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
can(
[
ProjectPermissionCmekActions.Create,
@@ -675,6 +711,9 @@ const buildViewerPermissionRules = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateAuthorities);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
return rules;
};
@@ -18,11 +18,11 @@ export const sshCertificateTemplateDALFactory = (db: TDbClient) => {
`${TableName.SshCertificateAuthority}.id`,
`${TableName.SshCertificateTemplate}.sshCaId`
)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.SshCertificateAuthority}.orgId`)
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.SshCertificateAuthority}.projectId`)
.where(`${TableName.SshCertificateTemplate}.id`, "=", id)
.select(selectAllTableCols(TableName.SshCertificateTemplate))
.select(
db.ref("orgId").withSchema(TableName.SshCertificateAuthority),
db.ref("projectId").withSchema(TableName.SshCertificateAuthority),
db.ref("friendlyName").as("caName").withSchema(TableName.SshCertificateAuthority),
db.ref("status").as("caStatus").withSchema(TableName.SshCertificateAuthority)
)
@@ -34,7 +34,10 @@ export const sshCertificateTemplateDALFactory = (db: TDbClient) => {
}
};
const getByName = async (name: string, orgId: string, tx?: Knex) => {
/**
* Returns the SSH certificate template named [name] within project with id [projectId]
*/
const getByName = async (name: string, projectId: string, tx?: Knex) => {
try {
const certTemplate = await (tx || db.replicaNode())(TableName.SshCertificateTemplate)
.join(
@@ -42,12 +45,12 @@ export const sshCertificateTemplateDALFactory = (db: TDbClient) => {
`${TableName.SshCertificateAuthority}.id`,
`${TableName.SshCertificateTemplate}.sshCaId`
)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.SshCertificateAuthority}.orgId`)
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.SshCertificateAuthority}.projectId`)
.where(`${TableName.SshCertificateTemplate}.name`, "=", name)
.where(`${TableName.Organization}.id`, "=", orgId)
.where(`${TableName.Project}.id`, "=", projectId)
.select(selectAllTableCols(TableName.SshCertificateTemplate))
.select(
db.ref("orgId").withSchema(TableName.SshCertificateAuthority),
db.ref("projectId").withSchema(TableName.SshCertificateAuthority),
db.ref("friendlyName").as("caName").withSchema(TableName.SshCertificateAuthority),
db.ref("status").as("caStatus").withSchema(TableName.SshCertificateAuthority)
)
@@ -1,8 +1,8 @@
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TSshCertificateAuthorityDALFactory } from "../ssh/ssh-certificate-authority-dal";
@@ -21,7 +21,7 @@ type TSshCertificateTemplateServiceFactoryDep = {
"transaction" | "getByName" | "create" | "updateById" | "deleteById" | "getById"
>;
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findById">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TSshCertificateTemplateServiceFactory = ReturnType<typeof sshCertificateTemplateServiceFactory>;
@@ -53,17 +53,17 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
ca.orgId,
ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.SshCertificateTemplates
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificateTemplates
);
if (ms(ttl) > ms(maxTTL)) {
@@ -73,7 +73,7 @@ export const sshCertificateTemplateServiceFactory = ({
}
const newCertificateTemplate = await sshCertificateTemplateDAL.transaction(async (tx) => {
const existingTemplate = await sshCertificateTemplateDAL.getByName(name, ca.orgId, tx);
const existingTemplate = await sshCertificateTemplateDAL.getByName(name, ca.projectId, tx);
if (existingTemplate) {
throw new BadRequestError({
message: `SSH certificate template with name ${name} already exists`
@@ -125,22 +125,22 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
certTemplate.orgId,
certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.SshCertificateTemplates
ProjectPermissionActions.Edit,
ProjectPermissionSub.SshCertificateTemplates
);
const updatedCertificateTemplate = await sshCertificateTemplateDAL.transaction(async (tx) => {
if (name) {
const existingTemplate = await sshCertificateTemplateDAL.getByName(name, actorOrgId, tx);
const existingTemplate = await sshCertificateTemplateDAL.getByName(name, certTemplate.projectId, tx);
if (existingTemplate && existingTemplate.id !== id) {
throw new BadRequestError({
message: `SSH certificate template with name ${name} already exists`
@@ -175,7 +175,7 @@ export const sshCertificateTemplateServiceFactory = ({
return {
certificateTemplate: updatedCertificateTemplate,
orgId: certTemplate.orgId
projectId: certTemplate.projectId
};
};
@@ -193,17 +193,17 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
certificateTemplate.orgId,
certificateTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.SshCertificateTemplates
ProjectPermissionActions.Delete,
ProjectPermissionSub.SshCertificateTemplates
);
await sshCertificateTemplateDAL.deleteById(certificateTemplate.id);
@@ -219,17 +219,17 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
certTemplate.orgId,
certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.SshCertificateTemplates
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateTemplates
);
return certTemplate;
@@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TSshCertificateBodyDALFactory = ReturnType<typeof sshCertificateBodyDALFactory>;
export const sshCertificateBodyDALFactory = (db: TDbClient) => {
const sshCertificateBodyOrm = ormify(db, TableName.SshCertificateBody);
return sshCertificateBodyOrm;
};
@@ -8,7 +8,7 @@ export type TSshCertificateDALFactory = ReturnType<typeof sshCertificateDALFacto
export const sshCertificateDALFactory = (db: TDbClient) => {
const sshCertificateOrm = ormify(db, TableName.SshCertificate);
const countSshCertificatesInOrg = async (orgId: string) => {
const countSshCertificatesInProject = async (projectId: string) => {
try {
interface CountResult {
count: string;
@@ -21,18 +21,18 @@ export const sshCertificateDALFactory = (db: TDbClient) => {
`${TableName.SshCertificate}.sshCaId`,
`${TableName.SshCertificateAuthority}.id`
)
.join(TableName.Organization, `${TableName.SshCertificateAuthority}.orgId`, `${TableName.Organization}.id`)
.where(`${TableName.Organization}.id`, orgId);
.join(TableName.Project, `${TableName.SshCertificateAuthority}.projectId`, `${TableName.Project}.id`)
.where(`${TableName.Project}.id`, projectId);
const count = await query.count("*").first();
return parseInt((count as unknown as CountResult).count || "0", 10);
} catch (error) {
throw new DatabaseError({ error, name: "Count all SSH certificates in organization" });
throw new DatabaseError({ error, name: "Count all SSH certificates in project" });
}
};
return {
...sshCertificateOrm,
countSshCertificatesInOrg
countSshCertificatesInProject
};
};
@@ -2,7 +2,7 @@ import { SshCertificateAuthoritiesSchema } from "@app/db/schemas";
export const sanitizedSshCa = SshCertificateAuthoritiesSchema.pick({
id: true,
orgId: true,
projectId: true,
friendlyName: true,
status: true,
keyAlgorithm: true
@@ -1,13 +1,15 @@
import { ForbiddenError } from "@casl/ability";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
import { TSshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal";
import { TSshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-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 { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { SshCertTemplateStatus } from "../ssh-certificate-template/ssh-certificate-template-types";
import {
@@ -37,18 +39,25 @@ type TSshCertificateAuthorityServiceFactoryDep = {
>;
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "create" | "findOne">;
sshCertificateTemplateDAL: Pick<TSshCertificateTemplateDALFactory, "find" | "getByName">;
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey" | "getOrgKmsKeyId">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">;
sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">;
kmsService: Pick<
TKmsServiceFactory,
"generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey" | "getOrgKmsKeyId" | "createCipherPairWithDataKey"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TSshCertificateAuthorityServiceFactory = ReturnType<typeof sshCertificateAuthorityServiceFactory>;
// TODO: secretManagerEncryptor -> sshEncryptor (cc akhil)
export const sshCertificateAuthorityServiceFactory = ({
sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL,
sshCertificateTemplateDAL,
sshCertificateDAL,
sshCertificateBodyDAL,
kmsService,
permissionService
}: TSshCertificateAuthorityServiceFactoryDep) => {
@@ -56,6 +65,7 @@ export const sshCertificateAuthorityServiceFactory = ({
* Generates a new SSH CA
*/
const createSshCa = async ({
projectId,
friendlyName,
keyAlgorithm,
actorId,
@@ -63,23 +73,23 @@ export const sshCertificateAuthorityServiceFactory = ({
actor,
actorOrgId
}: TCreateSshCaDTO) => {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
actorOrgId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.SshCertificateAuthorities
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificateAuthorities
);
const newCa = await sshCertificateAuthorityDAL.transaction(async (tx) => {
const ca = await sshCertificateAuthorityDAL.create(
{
orgId: actorOrgId,
projectId,
friendlyName,
status: SshCaStatus.ACTIVE,
keyAlgorithm
@@ -89,19 +99,16 @@ export const sshCertificateAuthorityServiceFactory = ({
const { publicKey, privateKey } = createSshKeyPair(keyAlgorithm, ca.friendlyName);
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId);
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: orgKmsKeyId
});
const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({
plainText: Buffer.from(privateKey, "utf8")
// TODO: update to sshEncryptor
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId
});
await sshCertificateAuthoritySecretDAL.create(
{
sshCaId: ca.id,
encryptedPrivateKey
encryptedPrivateKey: secretManagerEncryptor({ plainText: Buffer.from(privateKey, "utf8") }).cipherTextBlob
},
tx
);
@@ -119,28 +126,28 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
ca.orgId,
ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.SshCertificateAuthorities
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateAuthorities
);
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id });
// decrypt secret
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgKmsKeyId
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId: ca.projectId
});
const decryptedCaPrivateKey = await kmsDecryptor({
const decryptedCaPrivateKey = secretManagerDecryptor({
cipherTextBlob: sshCaSecret.encryptedPrivateKey
});
@@ -158,13 +165,13 @@ export const sshCertificateAuthorityServiceFactory = ({
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id });
// decrypt secret
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(ca.orgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgKmsKeyId
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId: ca.projectId
});
const decryptedCaPrivateKey = await kmsDecryptor({
const decryptedCaPrivateKey = secretManagerDecryptor({
cipherTextBlob: sshCaSecret.encryptedPrivateKey
});
@@ -189,30 +196,30 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
ca.orgId,
ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.SshCertificateAuthorities
ProjectPermissionActions.Edit,
ProjectPermissionSub.SshCertificateAuthorities
);
const updatedCa = await sshCertificateAuthorityDAL.updateById(caId, { friendlyName, status });
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id });
// decrypt secret
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgKmsKeyId
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId: ca.projectId
});
const decryptedCaPrivateKey = await kmsDecryptor({
const decryptedCaPrivateKey = secretManagerDecryptor({
cipherTextBlob: sshCaSecret.encryptedPrivateKey
});
@@ -228,17 +235,17 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
ca.orgId,
ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Delete,
OrgPermissionSubjects.SshCertificateAuthorities
ProjectPermissionActions.Delete,
ProjectPermissionSub.SshCertificateAuthorities
);
const deletedCa = await sshCertificateAuthorityDAL.deleteById(caId);
@@ -251,6 +258,7 @@ export const sshCertificateAuthorityServiceFactory = ({
* SSH public key is signed using CA behind SSH certificate with name [templateName].
*/
const issueSshCreds = async ({
projectId,
templateName,
keyAlgorithm,
certType,
@@ -262,22 +270,25 @@ export const sshCertificateAuthorityServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TIssueSshCredsDTO) => {
const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, actorOrgId);
const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, projectId);
if (!sshCertificateTemplate) {
throw new NotFoundError({
message: "No SSH certificate template found with specified name"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
actorOrgId,
sshCertificateTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificates
);
if (sshCertificateTemplate.caStatus === SshCaStatus.DISABLED) {
throw new BadRequestError({
@@ -307,13 +318,13 @@ export const sshCertificateAuthorityServiceFactory = ({
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
// decrypt secret
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgKmsKeyId
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId
});
const decryptedCaPrivateKey = await kmsDecryptor({
const decryptedCaPrivateKey = secretManagerDecryptor({
cipherTextBlob: sshCaSecret.encryptedPrivateKey
});
@@ -329,16 +340,38 @@ export const sshCertificateAuthorityServiceFactory = ({
certType
});
await sshCertificateDAL.create({
sshCaId: sshCertificateTemplate.sshCaId,
sshCertificateTemplateId: sshCertificateTemplate.id,
serialNumber,
certType,
publicKey,
principals,
keyId,
notBefore: new Date(),
notAfter: new Date(Date.now() + ttl * 1000)
// TODO: update to sshEncryptor
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId: sshCertificateTemplate.projectId
});
const encryptedCertificate = secretManagerEncryptor({
plainText: Buffer.from(signedPublicKey, "utf8")
}).cipherTextBlob;
await sshCertificateDAL.transaction(async (tx) => {
const cert = await sshCertificateDAL.create(
{
sshCaId: sshCertificateTemplate.sshCaId,
sshCertificateTemplateId: sshCertificateTemplate.id,
serialNumber,
certType,
principals,
keyId,
notBefore: new Date(),
notAfter: new Date(Date.now() + ttl * 1000)
},
tx
);
await sshCertificateBodyDAL.create(
{
sshCertId: cert.id,
encryptedCertificate
},
tx
);
});
return {
@@ -357,6 +390,7 @@ export const sshCertificateAuthorityServiceFactory = ({
* using CA behind SSH certificate template with name [templateName]
*/
const signSshKey = async ({
projectId,
templateName,
publicKey,
certType,
@@ -368,22 +402,25 @@ export const sshCertificateAuthorityServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TSignSshKeyDTO) => {
const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, actorOrgId);
const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, projectId);
if (!sshCertificateTemplate) {
throw new NotFoundError({
message: "No SSH certificate template found with specified name"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
actorOrgId,
sshCertificateTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificates
);
if (sshCertificateTemplate.caStatus === SshCaStatus.DISABLED) {
throw new BadRequestError({
@@ -413,13 +450,13 @@ export const sshCertificateAuthorityServiceFactory = ({
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
// decrypt secret
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgKmsKeyId
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId
});
const decryptedCaPrivateKey = await kmsDecryptor({
const decryptedCaPrivateKey = secretManagerDecryptor({
cipherTextBlob: sshCaSecret.encryptedPrivateKey
});
@@ -432,16 +469,38 @@ export const sshCertificateAuthorityServiceFactory = ({
certType
});
await sshCertificateDAL.create({
sshCaId: sshCertificateTemplate.sshCaId,
sshCertificateTemplateId: sshCertificateTemplate.id,
serialNumber,
certType,
publicKey,
principals,
keyId,
notBefore: new Date(),
notAfter: new Date(Date.now() + ttl * 1000)
// TODO: update to sshEncryptor
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId: sshCertificateTemplate.projectId
});
const encryptedCertificate = secretManagerEncryptor({
plainText: Buffer.from(signedPublicKey, "utf8")
}).cipherTextBlob;
await sshCertificateDAL.transaction(async (tx) => {
const cert = await sshCertificateDAL.create(
{
sshCaId: sshCertificateTemplate.sshCaId,
sshCertificateTemplateId: sshCertificateTemplate.id,
serialNumber,
certType,
principals,
keyId,
notBefore: new Date(),
notAfter: new Date(Date.now() + ttl * 1000)
},
tx
);
await sshCertificateBodyDAL.create(
{
sshCertId: cert.id,
encryptedCertificate
},
tx
);
});
return { serialNumber, signedPublicKey, certificateTemplate: sshCertificateTemplate, ttl, keyId };
@@ -457,17 +516,17 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
actorOrgId,
ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.SshCertificateTemplates
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateTemplates
);
const certificateTemplates = await sshCertificateTemplateDAL.find({ sshCaId: caId });
@@ -1,4 +1,4 @@
import { TOrgPermission } from "@app/lib/types";
import { TProjectPermission } from "@app/lib/types";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
export enum SshCaStatus {
@@ -14,11 +14,11 @@ export enum SshCertType {
export type TCreateSshCaDTO = {
friendlyName: string;
keyAlgorithm: CertKeyAlgorithm;
} & Omit<TOrgPermission, "orgId">;
} & TProjectPermission;
export type TGetSshCaDTO = {
caId: string;
} & Omit<TOrgPermission, "orgId">;
} & Omit<TProjectPermission, "projectId">;
export type TGetSshCaPublicKeyDTO = {
caId: string;
@@ -28,11 +28,11 @@ export type TUpdateSshCaDTO = {
caId: string;
friendlyName?: string;
status?: SshCaStatus;
} & Omit<TOrgPermission, "orgId">;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteSshCaDTO = {
caId: string;
} & Omit<TOrgPermission, "orgId">;
} & Omit<TProjectPermission, "projectId">;
export type TIssueSshCredsDTO = {
templateName: string;
@@ -41,7 +41,7 @@ export type TIssueSshCredsDTO = {
principals: string[];
ttl?: string;
keyId?: string;
} & Omit<TOrgPermission, "orgId">;
} & TProjectPermission;
export type TSignSshKeyDTO = {
templateName: string;
@@ -50,11 +50,11 @@ export type TSignSshKeyDTO = {
principals: string[];
ttl?: string;
keyId?: string;
} & Omit<TOrgPermission, "orgId">;
} & TProjectPermission;
export type TGetSshCaCertificateTemplatesDTO = {
caId: string;
} & Omit<TOrgPermission, "orgId">;
} & Omit<TProjectPermission, "projectId">;
export type TCreateSshCertDTO = {
caPrivateKey: string;
+12 -12
View File
@@ -384,17 +384,6 @@ export const ORGANIZATIONS = {
},
LIST_GROUPS: {
organizationId: "The ID of the organization to list groups for."
},
LIST_SSH_CAS: {
organizationId: "The ID of the organization to list SSH CAs for."
},
LIST_SSH_CERTIFICATES: {
organizationId: "The ID of the organization to list SSH certificates for.",
offset: "The offset to start from. If you enter 10, it will start from the 10th SSH certificate.",
limit: "The number of SSH certificates to return."
},
LIST_SSH_CERTIFICATE_TEMPLATES: {
organizationId: "The ID of the organization to list SSH certificate templates for."
}
} as const;
@@ -455,7 +444,15 @@ export const PROJECTS = {
workspaceId: "The ID of the project to list integration auths for."
},
LIST_SSH_CAS: {
slug: "The slug of the project to list SSH CAs for."
projectId: "The ID of the project to list SSH CAs for."
},
LIST_SSH_CERTIFICATES: {
projectId: "The ID of the project to list SSH certificates for.",
offset: "The offset to start from. If you enter 10, it will start from the 10th SSH certificate.",
limit: "The number of SSH certificates to return."
},
LIST_SSH_CERTIFICATE_TEMPLATES: {
projectId: "The ID of the project to list SSH certificate templates for."
},
LIST_CAS: {
slug: "The slug of the project to list CAs for.",
@@ -1148,6 +1145,7 @@ export const AUDIT_LOG_STREAMS = {
export const SSH_CERTIFICATE_AUTHORITIES = {
CREATE: {
projectId: "The ID of the project to create the SSH CA in.",
friendlyName: "A friendly name for the SSH CA.",
keyAlgorithm: "The type of public key algorithm and size, in bits, of the key pair for the SSH CA."
},
@@ -1169,6 +1167,7 @@ export const SSH_CERTIFICATE_AUTHORITIES = {
sshCaId: "The ID of the SSH CA to get the certificate templates for."
},
SIGN_SSH_KEY: {
projectId: "The ID of the project to sign the SSH public key for.",
templateName: "The name of the SSH certificate template to sign the SSH public key with.",
publicKey: "The SSH public key to sign.",
certType: "The type of certificate to issue. This can be one of user or host.",
@@ -1179,6 +1178,7 @@ export const SSH_CERTIFICATE_AUTHORITIES = {
signedKey: "The SSH certificate or signed SSH public key."
},
ISSUE_SSH_CREDENTIALS: {
projectId: "The ID of the project to issue the SSH credentials for.",
templateName: "The name of the SSH certificate template to issue the SSH credentials with.",
keyAlgorithm: "The type of public key algorithm and size, in bits, of the key pair for the SSH CA.",
certType: "The type of certificate to issue. This can be one of user or host.",
+7 -4
View File
@@ -78,6 +78,7 @@ import { snapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/sna
import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal";
import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service";
import { sshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal";
import { sshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal";
import { sshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal";
import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service";
@@ -349,6 +350,7 @@ export const registerRoutes = async (
const dynamicSecretLeaseDAL = dynamicSecretLeaseDALFactory(db);
const sshCertificateDAL = sshCertificateDALFactory(db);
const sshCertificateBodyDAL = sshCertificateBodyDALFactory(db);
const sshCertificateAuthorityDAL = sshCertificateAuthorityDALFactory(db);
const sshCertificateAuthoritySecretDAL = sshCertificateAuthoritySecretDALFactory(db);
const sshCertificateTemplateDAL = sshCertificateTemplateDALFactory(db);
@@ -565,10 +567,7 @@ export const registerRoutes = async (
groupDAL,
orgBotDAL,
oidcConfigDAL,
projectBotService,
sshCertificateAuthorityDAL,
sshCertificateDAL,
sshCertificateTemplateDAL
projectBotService
});
const signupService = authSignupServiceFactory({
tokenService,
@@ -721,6 +720,7 @@ export const registerRoutes = async (
sshCertificateAuthoritySecretDAL,
sshCertificateTemplateDAL,
sshCertificateDAL,
sshCertificateBodyDAL,
kmsService,
permissionService
});
@@ -817,6 +817,9 @@ export const registerRoutes = async (
certificateDAL,
pkiAlertDAL,
pkiCollectionDAL,
sshCertificateAuthorityDAL,
sshCertificateDAL,
sshCertificateTemplateDAL,
projectUserMembershipRoleDAL,
identityProjectMembershipRoleDAL,
keyStore,
@@ -11,9 +11,6 @@ import {
UsersSchema
} from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { sanitizedSshCa } from "@app/ee/services/ssh/ssh-certificate-authority-schema";
import { sanitizedSshCertificate } from "@app/ee/services/ssh-certificate/ssh-certificate-schema";
import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema";
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
import { getLastMidnightDateISO } from "@app/lib/fn";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -407,101 +404,4 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
return { groups };
}
});
server.route({
method: "GET",
url: "/:organizationId/ssh-certificates",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_SSH_CAS.organizationId)
}),
querystring: z.object({
offset: z.coerce.number().default(0).describe(ORGANIZATIONS.LIST_SSH_CERTIFICATES.offset),
limit: z.coerce.number().default(25).describe(ORGANIZATIONS.LIST_SSH_CERTIFICATES.limit)
}),
response: {
200: z.object({
certificates: z.array(sanitizedSshCertificate),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificates, totalCount } = await server.services.org.listOrgSshCertificates({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
orgId: req.params.organizationId,
offset: req.query.offset,
limit: req.query.limit
});
return { certificates, totalCount };
}
});
server.route({
method: "GET",
url: "/:organizationId/ssh-certificate-templates",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_SSH_CERTIFICATE_TEMPLATES.organizationId)
}),
response: {
200: z.object({
certificateTemplates: z.array(sanitizedSshCertificateTemplate)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificateTemplates } = await server.services.org.listOrgSshCertificateTemplates({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
orgId: req.params.organizationId
});
return { certificateTemplates };
}
});
server.route({
method: "GET",
url: "/:organizationId/ssh-cas",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_SSH_CAS.organizationId)
}),
response: {
200: z.object({
cas: z.array(sanitizedSshCa)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const cas = await server.services.org.listOrgSshCas({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
orgId: req.params.organizationId
});
return { cas };
}
});
};
@@ -10,6 +10,9 @@ import {
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types";
import { sanitizedSshCa } from "@app/ee/services/ssh/ssh-certificate-authority-schema";
import { sanitizedSshCertificate } from "@app/ee/services/ssh-certificate/ssh-certificate-schema";
import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema";
import { PROJECTS } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
@@ -517,4 +520,101 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
return { certificateTemplates };
}
});
server.route({
method: "GET",
url: "/:projectId/ssh-certificates",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(PROJECTS.LIST_SSH_CAS.projectId)
}),
querystring: z.object({
offset: z.coerce.number().default(0).describe(PROJECTS.LIST_SSH_CERTIFICATES.offset),
limit: z.coerce.number().default(25).describe(PROJECTS.LIST_SSH_CERTIFICATES.limit)
}),
response: {
200: z.object({
certificates: z.array(sanitizedSshCertificate),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificates, totalCount } = await server.services.project.listProjectSshCertificates({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
projectId: req.params.projectId,
offset: req.query.offset,
limit: req.query.limit
});
return { certificates, totalCount };
}
});
server.route({
method: "GET",
url: "/:projectId/ssh-certificate-templates",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(PROJECTS.LIST_SSH_CERTIFICATE_TEMPLATES.projectId)
}),
response: {
200: z.object({
certificateTemplates: z.array(sanitizedSshCertificateTemplate)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificateTemplates } = await server.services.project.listProjectSshCertificateTemplates({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
projectId: req.params.projectId
});
return { certificateTemplates };
}
});
server.route({
method: "GET",
url: "/:projectId/ssh-cas",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(PROJECTS.LIST_SSH_CAS.projectId)
}),
response: {
200: z.object({
cas: z.array(sanitizedSshCa)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const cas = await server.services.project.listProjectSshCas({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
projectId: req.params.projectId
});
return { cas };
}
});
};
+1 -101
View File
@@ -24,9 +24,6 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-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 { getConfig } from "@app/lib/config/env";
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
@@ -65,9 +62,6 @@ import {
TGetOrgGroupsDTO,
TGetOrgMembershipDTO,
TInviteUserToOrgDTO,
TListOrgSshCasDTO,
TListOrgSshCertificatesDTO,
TListOrgSshCertificateTemplatesDTO,
TListProjectMembershipsByOrgMembershipIdDTO,
TUpdateOrgDTO,
TUpdateOrgMembershipDTO,
@@ -104,9 +98,6 @@ type TOrgServiceFactoryDep = {
projectBotDAL: Pick<TProjectBotDALFactory, "findOne" | "updateById">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "create">;
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "find">;
sshCertificateDAL: Pick<TSshCertificateDALFactory, "find" | "countSshCertificatesInOrg">;
sshCertificateTemplateDAL: Pick<TSshCertificateTemplateDALFactory, "find">;
};
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
@@ -134,9 +125,6 @@ export const orgServiceFactory = ({
projectBotDAL,
projectUserMembershipRoleDAL,
identityMetadataDAL,
sshCertificateAuthorityDAL,
sshCertificateDAL,
sshCertificateTemplateDAL,
projectBotService
}: TOrgServiceFactoryDep) => {
/*
@@ -1139,91 +1127,6 @@ export const orgServiceFactory = ({
return incidentContact;
};
/**
* Return list of SSH CAs for organization
*/
const listOrgSshCas = async ({ actorId, actorOrgId, actorAuthMethod, actor, orgId }: TListOrgSshCasDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.SshCertificateAuthorities
);
const cas = await sshCertificateAuthorityDAL.find(
{
orgId
},
{ sort: [["updatedAt", "desc"]] }
);
return cas;
};
/**
* Return list of SSH certificates for organization
*/
const listOrgSshCertificates = async ({
limit = 25,
offset = 0,
actorId,
actorOrgId,
actorAuthMethod,
actor,
orgId
}: TListOrgSshCertificatesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificates);
const cas = await sshCertificateAuthorityDAL.find({
orgId
});
const certificates = await sshCertificateDAL.find(
{
$in: {
sshCaId: cas.map((ca) => ca.id)
}
},
{ offset, limit, sort: [["updatedAt", "desc"]] }
);
const count = await sshCertificateDAL.countSshCertificatesInOrg(orgId);
return { certificates, totalCount: count };
};
/**
* Return list of SSH certificate templates for organization
*/
const listOrgSshCertificateTemplates = async ({
actorId,
actorOrgId,
actorAuthMethod,
actor,
orgId
}: TListOrgSshCertificateTemplatesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.SshCertificateTemplates
);
const cas = await sshCertificateAuthorityDAL.find({
orgId
});
const certificateTemplates = await sshCertificateTemplateDAL.find({
$in: {
sshCaId: cas.map((ca) => ca.id)
}
});
return { certificateTemplates };
};
return {
findOrganizationById,
findAllOrgMembers,
@@ -1245,9 +1148,6 @@ export const orgServiceFactory = ({
deleteIncidentContact,
getOrgGroups,
listProjectMembershipsByOrgMembershipId,
findOrgBySlug,
listOrgSshCas,
listOrgSshCertificates,
listOrgSshCertificateTemplates
findOrgBySlug
};
};
-7
View File
@@ -75,13 +75,6 @@ export type TListProjectMembershipsByOrgMembershipIdDTO = {
orgMembershipId: string;
} & TOrgPermission;
export type TListOrgSshCasDTO = TOrgPermission;
export type TListOrgSshCertificateTemplatesDTO = TOrgPermission;
export type TListOrgSshCertificatesDTO = {
offset: number;
limit: number;
} & TOrgPermission;
export enum OrgAuthMethod {
OIDC = "oidc",
SAML = "saml"
@@ -8,6 +8,9 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { ProjectPermissionActions, 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 { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal";
import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
@@ -53,6 +56,9 @@ import {
TListProjectCertificateTemplatesDTO,
TListProjectCertsDTO,
TListProjectsDTO,
TListProjectSshCasDTO,
TListProjectSshCertificatesDTO,
TListProjectSshCertificateTemplatesDTO,
TLoadProjectKmsBackupDTO,
TToggleProjectAutoCapitalizationDTO,
TUpdateAuditLogsRetentionDTO,
@@ -90,6 +96,9 @@ type TProjectServiceFactoryDep = {
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getCertTemplatesByProjectId">;
pkiAlertDAL: Pick<TPkiAlertDALFactory, "find">;
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "find">;
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "find">;
sshCertificateDAL: Pick<TSshCertificateDALFactory, "find" | "countSshCertificatesInProject">;
sshCertificateTemplateDAL: Pick<TSshCertificateTemplateDALFactory, "find">;
permissionService: TPermissionServiceFactory;
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -133,6 +142,9 @@ export const projectServiceFactory = ({
certificateTemplateDAL,
pkiCollectionDAL,
pkiAlertDAL,
sshCertificateAuthorityDAL,
sshCertificateDAL,
sshCertificateTemplateDAL,
keyStore,
kmsService,
projectBotDAL,
@@ -859,6 +871,115 @@ export const projectServiceFactory = ({
};
};
/**
* Return list of SSH CAs for project
*/
const listProjectSshCas = async ({
actorId,
actorOrgId,
actorAuthMethod,
actor,
projectId
}: TListProjectSshCasDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateAuthorities
);
const cas = await sshCertificateAuthorityDAL.find(
{
projectId
},
{ sort: [["updatedAt", "desc"]] }
);
return cas;
};
/**
* Return list of SSH certificates for organization
*/
const listProjectSshCertificates = async ({
limit = 25,
offset = 0,
actorId,
actorOrgId,
actorAuthMethod,
actor,
projectId
}: TListProjectSshCertificatesDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
const cas = await sshCertificateAuthorityDAL.find({
projectId
});
const certificates = await sshCertificateDAL.find(
{
$in: {
sshCaId: cas.map((ca) => ca.id)
}
},
{ offset, limit, sort: [["updatedAt", "desc"]] }
);
const count = await sshCertificateDAL.countSshCertificatesInProject(projectId);
return { certificates, totalCount: count };
};
/**
* Return list of SSH certificate templates for organization
*/
const listProjectSshCertificateTemplates = async ({
actorId,
actorOrgId,
actorAuthMethod,
actor,
projectId
}: TListProjectSshCertificateTemplatesDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateTemplates
);
const cas = await sshCertificateAuthorityDAL.find({
projectId
});
const certificateTemplates = await sshCertificateTemplateDAL.find({
$in: {
sshCaId: cas.map((ca) => ca.id)
}
});
return { certificateTemplates };
};
const updateProjectKmsKey = async ({
projectId,
kms,
@@ -1092,6 +1213,9 @@ export const projectServiceFactory = ({
listProjectAlerts,
listProjectPkiCollections,
listProjectCertificateTemplates,
listProjectSshCas,
listProjectSshCertificates,
listProjectSshCertificateTemplates,
updateVersionLimit,
updateAuditLogsRetention,
updateProjectKmsKey,
@@ -130,6 +130,13 @@ export type TGetProjectKmsKey = TProjectPermission;
export type TListProjectCertificateTemplatesDTO = TProjectPermission;
export type TListProjectSshCasDTO = TProjectPermission;
export type TListProjectSshCertificateTemplatesDTO = TProjectPermission;
export type TListProjectSshCertificatesDTO = {
offset: number;
limit: number;
} & TProjectPermission;
export type TGetProjectSlackConfig = TProjectPermission;
export type TUpdateProjectSlackConfig = {