mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 05:27:52 +00:00
Add SSH certificate tab + data structure
This commit is contained in:
Vendored
+8
@@ -317,6 +317,9 @@ import {
|
|||||||
TSshCertificateAuthoritySecrets,
|
TSshCertificateAuthoritySecrets,
|
||||||
TSshCertificateAuthoritySecretsInsert,
|
TSshCertificateAuthoritySecretsInsert,
|
||||||
TSshCertificateAuthoritySecretsUpdate,
|
TSshCertificateAuthoritySecretsUpdate,
|
||||||
|
TSshCertificates,
|
||||||
|
TSshCertificatesInsert,
|
||||||
|
TSshCertificatesUpdate,
|
||||||
TSshCertificateTemplates,
|
TSshCertificateTemplates,
|
||||||
TSshCertificateTemplatesInsert,
|
TSshCertificateTemplatesInsert,
|
||||||
TSshCertificateTemplatesUpdate,
|
TSshCertificateTemplatesUpdate,
|
||||||
@@ -396,6 +399,11 @@ declare module "knex/types/tables" {
|
|||||||
TSshCertificateTemplatesInsert,
|
TSshCertificateTemplatesInsert,
|
||||||
TSshCertificateTemplatesUpdate
|
TSshCertificateTemplatesUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SshCertificate]: KnexOriginal.CompositeTableType<
|
||||||
|
TSshCertificates,
|
||||||
|
TSshCertificatesInsert,
|
||||||
|
TSshCertificatesUpdate
|
||||||
|
>;
|
||||||
[TableName.CertificateAuthority]: KnexOriginal.CompositeTableType<
|
[TableName.CertificateAuthority]: KnexOriginal.CompositeTableType<
|
||||||
TCertificateAuthorities,
|
TCertificateAuthorities,
|
||||||
TCertificateAuthoritiesInsert,
|
TCertificateAuthoritiesInsert,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
t.uuid("sshCaId").notNullable();
|
t.uuid("sshCaId").notNullable();
|
||||||
t.foreign("sshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
|
t.foreign("sshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
|
||||||
t.string("name").notNullable(); // note: how do we handle this being unique? across orgs?
|
t.string("name").notNullable();
|
||||||
t.string("ttl").notNullable();
|
t.string("ttl").notNullable();
|
||||||
t.string("maxTTL").notNullable();
|
t.string("maxTTL").notNullable();
|
||||||
t.specificType("allowedUsers", "text[]").notNullable();
|
t.specificType("allowedUsers", "text[]").notNullable();
|
||||||
@@ -45,9 +45,34 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
await createOnUpdateTrigger(knex, TableName.SshCertificateTemplate);
|
await createOnUpdateTrigger(knex, TableName.SshCertificateTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SshCertificate))) {
|
||||||
|
await knex.schema.createTable(TableName.SshCertificate, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("sshCaId").notNullable();
|
||||||
|
t.foreign("sshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
|
||||||
|
t.uuid("sshCertificateTemplateId");
|
||||||
|
t.foreign("sshCertificateTemplateId")
|
||||||
|
.references("id")
|
||||||
|
.inTable(TableName.SshCertificateTemplate)
|
||||||
|
.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();
|
||||||
|
t.datetime("notAfter").notNullable();
|
||||||
|
});
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SshCertificateTemplate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SshCertificate);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SshCertificate);
|
||||||
|
|
||||||
await knex.schema.dropTableIfExists(TableName.SshCertificateTemplate);
|
await knex.schema.dropTableIfExists(TableName.SshCertificateTemplate);
|
||||||
await dropOnUpdateTrigger(knex, TableName.SshCertificateTemplate);
|
await dropOnUpdateTrigger(knex, TableName.SshCertificateTemplate);
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ export * from "./slack-integrations";
|
|||||||
export * from "./ssh-certificate-authorities";
|
export * from "./ssh-certificate-authorities";
|
||||||
export * from "./ssh-certificate-authority-secrets";
|
export * from "./ssh-certificate-authority-secrets";
|
||||||
export * from "./ssh-certificate-templates";
|
export * from "./ssh-certificate-templates";
|
||||||
|
export * from "./ssh-certificates";
|
||||||
export * from "./super-admin";
|
export * from "./super-admin";
|
||||||
export * from "./totp-configs";
|
export * from "./totp-configs";
|
||||||
export * from "./trusted-ips";
|
export * from "./trusted-ips";
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export enum TableName {
|
|||||||
SshCertificateAuthority = "ssh_certificate_authorities",
|
SshCertificateAuthority = "ssh_certificate_authorities",
|
||||||
SshCertificateAuthoritySecret = "ssh_certificate_authority_secrets",
|
SshCertificateAuthoritySecret = "ssh_certificate_authority_secrets",
|
||||||
SshCertificateTemplate = "ssh_certificate_templates",
|
SshCertificateTemplate = "ssh_certificate_templates",
|
||||||
|
SshCertificate = "ssh_certificates",
|
||||||
CertificateAuthority = "certificate_authorities",
|
CertificateAuthority = "certificate_authorities",
|
||||||
CertificateTemplateEstConfig = "certificate_template_est_configs",
|
CertificateTemplateEstConfig = "certificate_template_est_configs",
|
||||||
CertificateAuthorityCert = "certificate_authority_certs",
|
CertificateAuthorityCert = "certificate_authority_certs",
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// 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 SshCertificatesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
sshCaId: z.string().uuid(),
|
||||||
|
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(),
|
||||||
|
notAfter: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSshCertificates = z.infer<typeof SshCertificatesSchema>;
|
||||||
|
export type TSshCertificatesInsert = Omit<z.input<typeof SshCertificatesSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSshCertificatesUpdate = Partial<Omit<z.input<typeof SshCertificatesSchema>, TImmutableDBKeys>>;
|
||||||
@@ -7,15 +7,6 @@ export enum OrgPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrgPermissionSshCertificateTemplateActions {
|
|
||||||
Read = "read",
|
|
||||||
Create = "create",
|
|
||||||
Edit = "edit",
|
|
||||||
Delete = "delete",
|
|
||||||
SignSshKey = "sign-ssh-key",
|
|
||||||
IssueSshCredentials = "issue-ssh-credentials"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum OrgPermissionAdminConsoleAction {
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
AccessAllProjects = "access-all-projects"
|
AccessAllProjects = "access-all-projects"
|
||||||
}
|
}
|
||||||
@@ -37,6 +28,7 @@ export enum OrgPermissionSubjects {
|
|||||||
AdminConsole = "organization-admin-console",
|
AdminConsole = "organization-admin-console",
|
||||||
AuditLogs = "audit-logs",
|
AuditLogs = "audit-logs",
|
||||||
ProjectTemplates = "project-templates",
|
ProjectTemplates = "project-templates",
|
||||||
|
SshCertificates = "ssh-certificates",
|
||||||
SshCertificateAuthorities = "ssh-certificate-authorities",
|
SshCertificateAuthorities = "ssh-certificate-authorities",
|
||||||
SshCertificateTemplates = "ssh-certificate-templates"
|
SshCertificateTemplates = "ssh-certificate-templates"
|
||||||
}
|
}
|
||||||
@@ -59,7 +51,8 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificateAuthorities]
|
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificateAuthorities]
|
||||||
| [OrgPermissionSshCertificateTemplateActions, OrgPermissionSubjects.SshCertificateTemplates];
|
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificates]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificateTemplates];
|
||||||
|
|
||||||
const buildAdminPermission = () => {
|
const buildAdminPermission = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
@@ -136,22 +129,18 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificates);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateAuthorities);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateAuthorities);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificateAuthorities);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificateAuthorities);
|
||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SshCertificateAuthorities);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SshCertificateAuthorities);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SshCertificateAuthorities);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SshCertificateAuthorities);
|
||||||
|
|
||||||
can(
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateTemplates);
|
||||||
[
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificateTemplates);
|
||||||
OrgPermissionSshCertificateTemplateActions.Read,
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SshCertificateTemplates);
|
||||||
OrgPermissionSshCertificateTemplateActions.Create,
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SshCertificateTemplates);
|
||||||
OrgPermissionSshCertificateTemplateActions.Edit,
|
|
||||||
OrgPermissionSshCertificateTemplateActions.Delete,
|
|
||||||
OrgPermissionSshCertificateTemplateActions.SignSshKey,
|
|
||||||
OrgPermissionSshCertificateTemplateActions.IssueSshCredentials
|
|
||||||
],
|
|
||||||
OrgPermissionSubjects.SshCertificateTemplates
|
|
||||||
);
|
|
||||||
|
|
||||||
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||||
|
|
||||||
@@ -184,9 +173,9 @@ const buildMemberPermission = () => {
|
|||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateAuthorities);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateAuthorities);
|
||||||
can(OrgPermissionSshCertificateTemplateActions.Read, OrgPermissionSubjects.SshCertificateTemplates);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificates);
|
||||||
can(OrgPermissionSshCertificateTemplateActions.SignSshKey, OrgPermissionSubjects.SshCertificateTemplates);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates);
|
||||||
can(OrgPermissionSshCertificateTemplateActions.IssueSshCredentials, OrgPermissionSubjects.SshCertificateTemplates);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateTemplates);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-8
@@ -1,10 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import {
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
OrgPermissionSshCertificateTemplateActions,
|
|
||||||
OrgPermissionSubjects
|
|
||||||
} from "@app/ee/services/permission/org-permission";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -61,7 +58,7 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionSshCertificateTemplateActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.SshCertificateTemplates
|
OrgPermissionSubjects.SshCertificateTemplates
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -124,7 +121,7 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionSshCertificateTemplateActions.Edit,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.SshCertificateTemplates
|
OrgPermissionSubjects.SshCertificateTemplates
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -183,7 +180,7 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionSshCertificateTemplateActions.Delete,
|
OrgPermissionActions.Delete,
|
||||||
OrgPermissionSubjects.SshCertificateTemplates
|
OrgPermissionSubjects.SshCertificateTemplates
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -209,7 +206,7 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionSshCertificateTemplateActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.SshCertificateTemplates
|
OrgPermissionSubjects.SshCertificateTemplates
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TSshCertificateDALFactory = ReturnType<typeof sshCertificateDALFactory>;
|
||||||
|
|
||||||
|
export const sshCertificateDALFactory = (db: TDbClient) => {
|
||||||
|
const sshCertificateOrm = ormify(db, TableName.SshCertificate);
|
||||||
|
|
||||||
|
const countSshCertificatesInOrg = async (orgId: string) => {
|
||||||
|
try {
|
||||||
|
interface CountResult {
|
||||||
|
count: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = db
|
||||||
|
.replicaNode()(TableName.SshCertificate)
|
||||||
|
.join(
|
||||||
|
TableName.SshCertificateAuthority,
|
||||||
|
`${TableName.SshCertificate}.sshCaId`,
|
||||||
|
`${TableName.SshCertificateAuthority}.id`
|
||||||
|
)
|
||||||
|
.join(TableName.Organization, `${TableName.SshCertificateAuthority}.orgId`, `${TableName.Organization}.id`)
|
||||||
|
.where(`${TableName.Organization}.id`, orgId);
|
||||||
|
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...sshCertificateOrm,
|
||||||
|
countSshCertificatesInOrg
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { SshCertificatesSchema } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export const sanitizedSshCertificate = SshCertificatesSchema.pick({
|
||||||
|
id: true,
|
||||||
|
sshCaId: true,
|
||||||
|
sshCertificateTemplateId: true,
|
||||||
|
serialNumber: true,
|
||||||
|
certType: true,
|
||||||
|
publicKey: true,
|
||||||
|
principals: true,
|
||||||
|
keyId: true
|
||||||
|
});
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import {
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
OrgPermissionActions,
|
|
||||||
OrgPermissionSshCertificateTemplateActions,
|
|
||||||
OrgPermissionSubjects
|
|
||||||
} from "@app/ee/services/permission/org-permission";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||||
import { TSshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-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 { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -38,6 +35,7 @@ type TSshCertificateAuthorityServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "create" | "findOne">;
|
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "create" | "findOne">;
|
||||||
sshCertificateTemplateDAL: Pick<TSshCertificateTemplateDALFactory, "find" | "getByName">;
|
sshCertificateTemplateDAL: Pick<TSshCertificateTemplateDALFactory, "find" | "getByName">;
|
||||||
|
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey" | "getOrgKmsKeyId">;
|
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey" | "getOrgKmsKeyId">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
};
|
};
|
||||||
@@ -48,6 +46,7 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
sshCertificateAuthoritySecretDAL,
|
sshCertificateAuthoritySecretDAL,
|
||||||
sshCertificateTemplateDAL,
|
sshCertificateTemplateDAL,
|
||||||
|
sshCertificateDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
permissionService
|
permissionService
|
||||||
}: TSshCertificateAuthorityServiceFactoryDep) => {
|
}: TSshCertificateAuthorityServiceFactoryDep) => {
|
||||||
@@ -252,10 +251,13 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates);
|
||||||
OrgPermissionSshCertificateTemplateActions.IssueSshCredentials,
|
|
||||||
OrgPermissionSubjects.SshCertificateTemplates
|
if (sshCertificateTemplate.caStatus === SshCaStatus.DISABLED) {
|
||||||
);
|
throw new BadRequestError({
|
||||||
|
message: "SSH CA is disabled"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// validate if the requested [certType] is allowed under the template configuration
|
// validate if the requested [certType] is allowed under the template configuration
|
||||||
validateSshCertificateType(sshCertificateTemplate, certType);
|
validateSshCertificateType(sshCertificateTemplate, certType);
|
||||||
@@ -295,6 +297,18 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
certType
|
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)
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
serialNumber,
|
serialNumber,
|
||||||
signedPublicKey,
|
signedPublicKey,
|
||||||
@@ -337,10 +351,13 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates);
|
||||||
OrgPermissionSshCertificateTemplateActions.SignSshKey,
|
|
||||||
OrgPermissionSubjects.SshCertificateTemplates
|
if (sshCertificateTemplate.caStatus === SshCaStatus.DISABLED) {
|
||||||
);
|
throw new BadRequestError({
|
||||||
|
message: "SSH CA is disabled"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// validate if the requested [certType] is allowed under the template configuration
|
// validate if the requested [certType] is allowed under the template configuration
|
||||||
validateSshCertificateType(sshCertificateTemplate, certType);
|
validateSshCertificateType(sshCertificateTemplate, certType);
|
||||||
@@ -377,6 +394,18 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
certType
|
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)
|
||||||
|
});
|
||||||
|
|
||||||
return { serialNumber, signedPublicKey, certificateTemplate: sshCertificateTemplate, ttl, keyId };
|
return { serialNumber, signedPublicKey, certificateTemplate: sshCertificateTemplate, ttl, keyId };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -399,7 +428,7 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionSshCertificateTemplateActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.SshCertificateTemplates
|
OrgPermissionSubjects.SshCertificateTemplates
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -387,6 +387,14 @@ export const ORGANIZATIONS = {
|
|||||||
},
|
},
|
||||||
LIST_SSH_CAS: {
|
LIST_SSH_CAS: {
|
||||||
organizationId: "The ID of the organization to list SSH CAs for."
|
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;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -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 { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||||
import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-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 { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service";
|
||||||
|
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 { 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";
|
import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service";
|
||||||
import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal";
|
import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal";
|
||||||
@@ -347,6 +348,7 @@ export const registerRoutes = async (
|
|||||||
const dynamicSecretDAL = dynamicSecretDALFactory(db);
|
const dynamicSecretDAL = dynamicSecretDALFactory(db);
|
||||||
const dynamicSecretLeaseDAL = dynamicSecretLeaseDALFactory(db);
|
const dynamicSecretLeaseDAL = dynamicSecretLeaseDALFactory(db);
|
||||||
|
|
||||||
|
const sshCertificateDAL = sshCertificateDALFactory(db);
|
||||||
const sshCertificateAuthorityDAL = sshCertificateAuthorityDALFactory(db);
|
const sshCertificateAuthorityDAL = sshCertificateAuthorityDALFactory(db);
|
||||||
const sshCertificateAuthoritySecretDAL = sshCertificateAuthoritySecretDALFactory(db);
|
const sshCertificateAuthoritySecretDAL = sshCertificateAuthoritySecretDALFactory(db);
|
||||||
const sshCertificateTemplateDAL = sshCertificateTemplateDALFactory(db);
|
const sshCertificateTemplateDAL = sshCertificateTemplateDALFactory(db);
|
||||||
@@ -564,7 +566,9 @@ export const registerRoutes = async (
|
|||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
oidcConfigDAL,
|
oidcConfigDAL,
|
||||||
projectBotService,
|
projectBotService,
|
||||||
sshCertificateAuthorityDAL
|
sshCertificateAuthorityDAL,
|
||||||
|
sshCertificateDAL,
|
||||||
|
sshCertificateTemplateDAL
|
||||||
});
|
});
|
||||||
const signupService = authSignupServiceFactory({
|
const signupService = authSignupServiceFactory({
|
||||||
tokenService,
|
tokenService,
|
||||||
@@ -716,6 +720,7 @@ export const registerRoutes = async (
|
|||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
sshCertificateAuthoritySecretDAL,
|
sshCertificateAuthoritySecretDAL,
|
||||||
sshCertificateTemplateDAL,
|
sshCertificateTemplateDAL,
|
||||||
|
sshCertificateDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { sanitizedSshCa } from "@app/ee/services/ssh/ssh-certificate-authority-schema";
|
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 { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { getLastMidnightDateISO } from "@app/lib/fn";
|
import { getLastMidnightDateISO } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -406,6 +408,73 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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() // TODO
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:organizationId/ssh-cas",
|
url: "/:organizationId/ssh-cas",
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
|
|||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
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 { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
||||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-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 { getConfig } from "@app/lib/config/env";
|
||||||
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
||||||
import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
@@ -64,6 +66,8 @@ import {
|
|||||||
TGetOrgMembershipDTO,
|
TGetOrgMembershipDTO,
|
||||||
TInviteUserToOrgDTO,
|
TInviteUserToOrgDTO,
|
||||||
TListOrgSshCasDTO,
|
TListOrgSshCasDTO,
|
||||||
|
TListOrgSshCertificatesDTO,
|
||||||
|
TListOrgSshCertificateTemplatesDTO,
|
||||||
TListProjectMembershipsByOrgMembershipIdDTO,
|
TListProjectMembershipsByOrgMembershipIdDTO,
|
||||||
TUpdateOrgDTO,
|
TUpdateOrgDTO,
|
||||||
TUpdateOrgMembershipDTO,
|
TUpdateOrgMembershipDTO,
|
||||||
@@ -101,6 +105,8 @@ type TOrgServiceFactoryDep = {
|
|||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "create">;
|
||||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||||
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "find">;
|
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "find">;
|
||||||
|
sshCertificateDAL: Pick<TSshCertificateDALFactory, "find" | "countSshCertificatesInOrg">;
|
||||||
|
sshCertificateTemplateDAL: Pick<TSshCertificateTemplateDALFactory, "find">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
||||||
@@ -129,6 +135,8 @@ export const orgServiceFactory = ({
|
|||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
identityMetadataDAL,
|
identityMetadataDAL,
|
||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
|
sshCertificateDAL,
|
||||||
|
sshCertificateTemplateDAL,
|
||||||
projectBotService
|
projectBotService
|
||||||
}: TOrgServiceFactoryDep) => {
|
}: TOrgServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
@@ -1132,7 +1140,7 @@ export const orgServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of SSH CAs for project
|
* Return list of SSH CAs for organization
|
||||||
*/
|
*/
|
||||||
const listOrgSshCas = async ({ actorId, actorOrgId, actorAuthMethod, actor, orgId }: TListOrgSshCasDTO) => {
|
const listOrgSshCas = async ({ actorId, actorOrgId, actorAuthMethod, actor, orgId }: TListOrgSshCasDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
@@ -1152,6 +1160,70 @@ export const orgServiceFactory = ({
|
|||||||
return cas;
|
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 {
|
return {
|
||||||
findOrganizationById,
|
findOrganizationById,
|
||||||
findAllOrgMembers,
|
findAllOrgMembers,
|
||||||
@@ -1174,6 +1246,8 @@ export const orgServiceFactory = ({
|
|||||||
getOrgGroups,
|
getOrgGroups,
|
||||||
listProjectMembershipsByOrgMembershipId,
|
listProjectMembershipsByOrgMembershipId,
|
||||||
findOrgBySlug,
|
findOrgBySlug,
|
||||||
listOrgSshCas
|
listOrgSshCas,
|
||||||
|
listOrgSshCertificates,
|
||||||
|
listOrgSshCertificateTemplates
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ export type TListProjectMembershipsByOrgMembershipIdDTO = {
|
|||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TListOrgSshCasDTO = TOrgPermission;
|
export type TListOrgSshCasDTO = TOrgPermission;
|
||||||
|
export type TListOrgSshCertificateTemplatesDTO = TOrgPermission;
|
||||||
|
export type TListOrgSshCertificatesDTO = {
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
export enum OrgAuthMethod {
|
export enum OrgAuthMethod {
|
||||||
OIDC = "oidc",
|
OIDC = "oidc",
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ export enum OrgPermissionSubjects {
|
|||||||
AuditLogs = "audit-logs",
|
AuditLogs = "audit-logs",
|
||||||
ProjectTemplates = "project-templates",
|
ProjectTemplates = "project-templates",
|
||||||
SshCertificateAuthorities = "ssh-certificate-authorities",
|
SshCertificateAuthorities = "ssh-certificate-authorities",
|
||||||
SshCertificateTemplates = "ssh-certificate-templates"
|
SshCertificateTemplates = "ssh-certificate-templates",
|
||||||
|
SshCertificates = "ssh-certificates"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrgPermissionAdminConsoleAction {
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
@@ -60,6 +61,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificateAuthorities]
|
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificateAuthorities]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.SshCertificates]
|
||||||
| [OrgPermissionSshCertificateTemplateActions, OrgPermissionSubjects.SshCertificateTemplates];
|
| [OrgPermissionSshCertificateTemplateActions, OrgPermissionSubjects.SshCertificateTemplates];
|
||||||
|
|
||||||
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
||||||
|
|||||||
@@ -20,5 +20,7 @@ export {
|
|||||||
useGetOrgTaxIds,
|
useGetOrgTaxIds,
|
||||||
useGetOrgTrialUrl,
|
useGetOrgTrialUrl,
|
||||||
useListOrgSshCas,
|
useListOrgSshCas,
|
||||||
|
useListOrgSshCertificates,
|
||||||
|
useListOrgSshCertificateTemplates,
|
||||||
useUpdateOrg,
|
useUpdateOrg,
|
||||||
useUpdateOrgBillingDetails} from "./queries";
|
useUpdateOrgBillingDetails} from "./queries";
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||||
|
|
||||||
import { TGroupOrgMembership } from "../groups/types";
|
import { TGroupOrgMembership } from "../groups/types";
|
||||||
import { TSshCertificateAuthority } from "../ssh-ca/types";
|
import { TSshCertificate,TSshCertificateAuthority } from "../ssh-ca/types";
|
||||||
|
import { TSshCertificateTemplate } from "../sshCertificateTemplates/types";
|
||||||
import { IntegrationAuth } from "../types";
|
import { IntegrationAuth } from "../types";
|
||||||
import {
|
import {
|
||||||
BillingDetails,
|
BillingDetails,
|
||||||
@@ -43,7 +44,11 @@ export const organizationKeys = {
|
|||||||
[...organizationKeys.getOrgIdentityMemberships(orgId), params] as const,
|
[...organizationKeys.getOrgIdentityMemberships(orgId), params] as const,
|
||||||
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const,
|
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const,
|
||||||
getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const,
|
getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const,
|
||||||
getOrgSshCas: ({ orgId }: { orgId: string }) => [{ orgId }, "org-ssh-cas"] as const
|
getOrgSshCas: ({ orgId }: { orgId: string }) => [{ orgId }, "org-ssh-cas"] as const,
|
||||||
|
allOrgSshCertificates: () => ["org-ssh-certificates"] as const,
|
||||||
|
specificOrgSshCertificates: ({ offset, limit }: { offset: number; limit: number }) =>
|
||||||
|
[...organizationKeys.allOrgSshCertificates(), { offset, limit }] as const,
|
||||||
|
getOrgSshCertificateTemplates: () => ["org-ssh-certificate-templates"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchOrganizations = async () => {
|
export const fetchOrganizations = async () => {
|
||||||
@@ -512,3 +517,48 @@ export const useListOrgSshCas = ({ orgId }: { orgId: string }) => {
|
|||||||
enabled: Boolean(orgId)
|
enabled: Boolean(orgId)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useListOrgSshCertificates = ({
|
||||||
|
orgId,
|
||||||
|
offset,
|
||||||
|
limit
|
||||||
|
}: {
|
||||||
|
orgId: string;
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
}) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: organizationKeys.specificOrgSshCertificates({
|
||||||
|
offset,
|
||||||
|
limit
|
||||||
|
}),
|
||||||
|
queryFn: async () => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
offset: String(offset),
|
||||||
|
limit: String(limit)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await apiRequest.get<{
|
||||||
|
certificates: TSshCertificate[];
|
||||||
|
totalCount: number;
|
||||||
|
}>(`/api/v1/organization/${orgId}/ssh-certificates`, {
|
||||||
|
params
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: Boolean(orgId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useListOrgSshCertificateTemplates = ({ orgId }: { orgId: string }) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: organizationKeys.getOrgSshCertificateTemplates(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{ certificateTemplates: TSshCertificateTemplate[] }>(
|
||||||
|
`/api/v1/organization/${orgId}/ssh-certificate-templates`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: Boolean(orgId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,3 +7,8 @@ export enum SshCertType {
|
|||||||
USER = "user",
|
USER = "user",
|
||||||
HOST = "host"
|
HOST = "host"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sshCertTypeToNameMap: { [K in SshCertType]: string } = {
|
||||||
|
[SshCertType.USER]: "User",
|
||||||
|
[SshCertType.HOST]: "Host"
|
||||||
|
};
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
export { SshCaStatus } from "./enums";
|
export { SshCaStatus } from "./constants";
|
||||||
export {
|
export {
|
||||||
useCreateSshCa,
|
useCreateSshCa,
|
||||||
useDeleteSshCa,
|
useDeleteSshCa,
|
||||||
useIssueSshCreds,
|
useIssueSshCreds,
|
||||||
useSignSshKey,
|
useSignSshKey,
|
||||||
useUpdateSshCa} from "./mutations";
|
useUpdateSshCa
|
||||||
|
} from "./mutations";
|
||||||
export { useGetSshCaById, useGetSshCaCertTemplates } from "./queries";
|
export { useGetSshCaById, useGetSshCaCertTemplates } from "./queries";
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import {
|
|||||||
TSignSshKeyDTO,
|
TSignSshKeyDTO,
|
||||||
TSignSshKeyResponse,
|
TSignSshKeyResponse,
|
||||||
TSshCertificateAuthority,
|
TSshCertificateAuthority,
|
||||||
TUpdateSshCaDTO} from "./types";
|
TUpdateSshCaDTO
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
export const sshCaKeys = {
|
export const sshCaKeys = {
|
||||||
getSshCaById: (caId: string) => [{ caId }, "ssh-ca"]
|
getSshCaById: (caId: string) => [{ caId }, "ssh-ca"]
|
||||||
@@ -64,19 +65,27 @@ export const useDeleteSshCa = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useSignSshKey = () => {
|
export const useSignSshKey = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
return useMutation<TSignSshKeyResponse, {}, TSignSshKeyDTO>({
|
return useMutation<TSignSshKeyResponse, {}, TSignSshKeyDTO>({
|
||||||
mutationFn: async (body) => {
|
mutationFn: async (body) => {
|
||||||
const { data } = await apiRequest.post<TSignSshKeyResponse>("/api/v1/ssh/sign", body);
|
const { data } = await apiRequest.post<TSignSshKeyResponse>("/api/v1/ssh/sign", body);
|
||||||
return data;
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(organizationKeys.allOrgSshCertificates());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useIssueSshCreds = () => {
|
export const useIssueSshCreds = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
return useMutation<TIssueSshCredsResponse, {}, TIssueSshCredsDTO>({
|
return useMutation<TIssueSshCredsResponse, {}, TIssueSshCredsDTO>({
|
||||||
mutationFn: async (body) => {
|
mutationFn: async (body) => {
|
||||||
const { data } = await apiRequest.post<TIssueSshCredsResponse>("/api/v1/ssh/issue", body);
|
const { data } = await apiRequest.post<TIssueSshCredsResponse>("/api/v1/ssh/issue", body);
|
||||||
return data;
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(organizationKeys.allOrgSshCertificates());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import { CertKeyAlgorithm } from "../certificates/enums";
|
import { CertKeyAlgorithm } from "../certificates/enums";
|
||||||
import { SshCaStatus, SshCertType } from "./enums";
|
import { SshCaStatus, SshCertType } from "./constants";
|
||||||
|
|
||||||
|
export type TSshCertificate = {
|
||||||
|
id: string;
|
||||||
|
sshCaId: string;
|
||||||
|
sshCertificateTemplateId: string;
|
||||||
|
serialNumber: string;
|
||||||
|
certType: SshCertType;
|
||||||
|
publicKey: string;
|
||||||
|
principals: string[];
|
||||||
|
keyId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TSshCertificateAuthority = {
|
export type TSshCertificateAuthority = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ import Head from "next/head";
|
|||||||
|
|
||||||
import { SshPage } from "@app/views/Org/SshPage";
|
import { SshPage } from "@app/views/Org/SshPage";
|
||||||
|
|
||||||
// TODO: update meta tags
|
|
||||||
|
|
||||||
const Ssh = () => {
|
const Ssh = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ const generalPermissionSchema = z
|
|||||||
})
|
})
|
||||||
.optional();
|
.optional();
|
||||||
|
|
||||||
|
const sshCertificateSchema = z
|
||||||
|
.object({
|
||||||
|
read: z.boolean().optional(),
|
||||||
|
create: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional();
|
||||||
|
|
||||||
const sshCertificateTemplatePermissionSchmea = z
|
const sshCertificateTemplatePermissionSchmea = z
|
||||||
.object({
|
.object({
|
||||||
read: z.boolean().optional(),
|
read: z.boolean().optional(),
|
||||||
@@ -62,6 +69,7 @@ export const formSchema = z.object({
|
|||||||
[OrgPermissionSubjects.Kms]: generalPermissionSchema,
|
[OrgPermissionSubjects.Kms]: generalPermissionSchema,
|
||||||
[OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema,
|
[OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema,
|
||||||
[OrgPermissionSubjects.SshCertificateAuthorities]: generalPermissionSchema,
|
[OrgPermissionSubjects.SshCertificateAuthorities]: generalPermissionSchema,
|
||||||
|
[OrgPermissionSubjects.SshCertificates]: sshCertificateSchema,
|
||||||
"ssh-certificate-templates": sshCertificateTemplatePermissionSchmea
|
"ssh-certificate-templates": sshCertificateTemplatePermissionSchmea
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
+7
@@ -51,6 +51,11 @@ const PROJECT_TEMPLATES_PERMISSIONS = [
|
|||||||
{ action: "delete", label: "Remove" }
|
{ action: "delete", label: "Remove" }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
const SSH_CERTIFICATES_PERMISSIONS = [
|
||||||
|
{ action: "read", label: "View" },
|
||||||
|
{ action: "create", label: "Create" }
|
||||||
|
] as const;
|
||||||
|
|
||||||
const getPermissionList = (option: string) => {
|
const getPermissionList = (option: string) => {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case "secret-scanning":
|
case "secret-scanning":
|
||||||
@@ -63,6 +68,8 @@ const getPermissionList = (option: string) => {
|
|||||||
return MEMBERS_PERMISSIONS;
|
return MEMBERS_PERMISSIONS;
|
||||||
case OrgPermissionSubjects.ProjectTemplates:
|
case OrgPermissionSubjects.ProjectTemplates:
|
||||||
return PROJECT_TEMPLATES_PERMISSIONS;
|
return PROJECT_TEMPLATES_PERMISSIONS;
|
||||||
|
case OrgPermissionSubjects.SshCertificates:
|
||||||
|
return SSH_CERTIFICATES_PERMISSIONS;
|
||||||
default:
|
default:
|
||||||
return PERMISSIONS;
|
return PERMISSIONS;
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-6
@@ -15,7 +15,6 @@ import {
|
|||||||
import { OrgPermissionAdminConsoleRow } from "./OrgPermissionAdminConsoleRow";
|
import { OrgPermissionAdminConsoleRow } from "./OrgPermissionAdminConsoleRow";
|
||||||
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
|
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
|
||||||
import { RolePermissionRow } from "./RolePermissionRow";
|
import { RolePermissionRow } from "./RolePermissionRow";
|
||||||
import { SshCertificateTemplateRow } from "./SshCertificateTemplateRow";
|
|
||||||
|
|
||||||
const SIMPLE_PERMISSION_OPTIONS = [
|
const SIMPLE_PERMISSION_OPTIONS = [
|
||||||
{
|
{
|
||||||
@@ -74,6 +73,14 @@ const SIMPLE_PERMISSION_OPTIONS = [
|
|||||||
{
|
{
|
||||||
title: "SSH Certificate Authorities",
|
title: "SSH Certificate Authorities",
|
||||||
formName: OrgPermissionSubjects.SshCertificateAuthorities
|
formName: OrgPermissionSubjects.SshCertificateAuthorities
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "SSH Certificates",
|
||||||
|
formName: OrgPermissionSubjects.SshCertificates
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "SSH Certificate Templates",
|
||||||
|
formName: OrgPermissionSubjects.SshCertificateTemplates
|
||||||
}
|
}
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
@@ -169,11 +176,6 @@ export const RolePermissionsSection = ({ roleId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<SshCertificateTemplateRow
|
|
||||||
control={control}
|
|
||||||
setValue={setValue}
|
|
||||||
isEditable={isCustomRole}
|
|
||||||
/>
|
|
||||||
<OrgRoleWorkspaceRow
|
<OrgRoleWorkspaceRow
|
||||||
control={control}
|
control={control}
|
||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
|
|||||||
-137
@@ -1,137 +0,0 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
|
||||||
import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
|
|
||||||
import { OrgPermissionSubjects } from "@app/context";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
import { TFormSchema } from "@app/views/Org/RolePage/components/OrgRoleModifySection.utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isEditable: boolean;
|
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
|
||||||
control: Control<TFormSchema>;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Permission {
|
|
||||||
NoAccess = "no-access",
|
|
||||||
Custom = "custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
const PERMISSION_ACTIONS = [
|
|
||||||
{ action: "read", label: "Read" },
|
|
||||||
{ action: "create", label: "Create" },
|
|
||||||
{ action: "edit", label: "Modify" },
|
|
||||||
{ action: "delete", label: "Remove" },
|
|
||||||
{ action: "sign-ssh-key", label: "Sign SSH Key" },
|
|
||||||
{ action: "issue-ssh-credentials", label: "Issue SSH Credentials" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const SshCertificateTemplateRow = ({ isEditable, control, setValue }: Props) => {
|
|
||||||
const [isRowExpanded, setIsRowExpanded] = useToggle();
|
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
|
||||||
|
|
||||||
const rule = useWatch({
|
|
||||||
control,
|
|
||||||
name: "permissions.ssh-certificate-templates"
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
|
||||||
if (rule?.create) {
|
|
||||||
return Permission.Custom;
|
|
||||||
}
|
|
||||||
return Permission.NoAccess;
|
|
||||||
}, [rule, isCustom]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
|
||||||
else setIsCustom.off();
|
|
||||||
}, [selectedPermissionCategory]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const isRowCustom = selectedPermissionCategory === Permission.Custom;
|
|
||||||
if (isRowCustom) {
|
|
||||||
setIsRowExpanded.on();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
|
||||||
if (!val) return;
|
|
||||||
if (val === Permission.Custom) {
|
|
||||||
setIsRowExpanded.on();
|
|
||||||
setIsCustom.on();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setIsCustom.off();
|
|
||||||
|
|
||||||
if (val === Permission.NoAccess) {
|
|
||||||
setValue("permissions.workspace", { create: false }, { shouldDirty: true });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Tr
|
|
||||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
|
||||||
onClick={() => setIsRowExpanded.toggle()}
|
|
||||||
>
|
|
||||||
<Td>
|
|
||||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
|
||||||
</Td>
|
|
||||||
<Td>SSH Certificate Templates</Td>
|
|
||||||
<Td>
|
|
||||||
<Select
|
|
||||||
value={selectedPermissionCategory}
|
|
||||||
className="w-40 bg-mineshaft-600"
|
|
||||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
|
||||||
onValueChange={handlePermissionChange}
|
|
||||||
isDisabled={!isEditable}
|
|
||||||
>
|
|
||||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
{isRowExpanded && (
|
|
||||||
<Tr>
|
|
||||||
<Td
|
|
||||||
colSpan={3}
|
|
||||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && " border-mineshaft-500 p-8"}`}
|
|
||||||
>
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
|
||||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
|
||||||
return (
|
|
||||||
<Controller
|
|
||||||
name={`permissions.${OrgPermissionSubjects.SshCertificateTemplates}.${action}`}
|
|
||||||
key={`permissions.${OrgPermissionSubjects.SshCertificateTemplates}.${action}`}
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
isChecked={field.value}
|
|
||||||
onCheckedChange={(e) => {
|
|
||||||
if (!isEditable) {
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Failed to update default role"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
field.onChange(e);
|
|
||||||
}}
|
|
||||||
id={`permissions.${OrgPermissionSubjects.SshCertificateTemplates}.${action}`}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -164,7 +164,7 @@ export const SshCertificateContent = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
<div className="flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
<p className="mr-4 whitespace-pre-wrap break-all">{publicKey}</p>
|
<p className="mr-4 whitespace-pre-wrap break-all">{publicKey}</p>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
SelectItem
|
SelectItem
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useGetSshCaCertTemplates, useIssueSshCreds, useSignSshKey } from "@app/hooks/api";
|
import { useOrganization } from "@app/context";
|
||||||
|
import { useIssueSshCreds, useListOrgSshCertificateTemplates, useSignSshKey } from "@app/hooks/api";
|
||||||
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
|
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
|
||||||
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
|
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
|
||||||
import { SshCertType } from "@app/hooks/api/ssh-ca/enums";
|
import { SshCertType } from "@app/hooks/api/ssh-ca/enums";
|
||||||
@@ -62,6 +63,7 @@ enum SshCertificateOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
const [operation, setOperation] = useState<SshCertificateOperation>(
|
const [operation, setOperation] = useState<SshCertificateOperation>(
|
||||||
SshCertificateOperation.SIGN_SSH_KEY
|
SshCertificateOperation.SIGN_SSH_KEY
|
||||||
);
|
);
|
||||||
@@ -72,7 +74,9 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
|
|
||||||
const popUpData = popUp?.sshCertificate?.data as { sshCaId: string; templateName: string };
|
const popUpData = popUp?.sshCertificate?.data as { sshCaId: string; templateName: string };
|
||||||
|
|
||||||
const { data: templatesData } = useGetSshCaCertTemplates(popUpData?.sshCaId || "");
|
const { data: templatesData } = useListOrgSshCertificateTemplates({
|
||||||
|
orgId: currentOrg?.id || ""
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -91,6 +95,8 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (popUpData) {
|
if (popUpData) {
|
||||||
setValue("templateName", popUpData.templateName);
|
setValue("templateName", popUpData.templateName);
|
||||||
|
} else if (templatesData && templatesData.certificateTemplates.length > 0) {
|
||||||
|
setValue("templateName", templatesData.certificateTemplates[0].name);
|
||||||
}
|
}
|
||||||
}, [popUpData]);
|
}, [popUpData]);
|
||||||
|
|
||||||
@@ -114,6 +120,7 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
ttl,
|
ttl,
|
||||||
keyId
|
keyId
|
||||||
});
|
});
|
||||||
|
|
||||||
setCertificateDetails({
|
setCertificateDetails({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
signedKey
|
signedKey
|
||||||
@@ -186,7 +193,7 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
{...field}
|
{...field}
|
||||||
onValueChange={(e) => onChange(e)}
|
onValueChange={(e) => onChange(e)}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
isDisabled
|
isDisabled={Boolean(popUpData?.sshCaId)}
|
||||||
>
|
>
|
||||||
{(templatesData?.certificateTemplates || []).map(({ id, name }) => (
|
{(templatesData?.certificateTemplates || []).map(({ id, name }) => (
|
||||||
<SelectItem value={name} key={`ssh-cert-template-${id}`}>
|
<SelectItem value={name} key={`ssh-cert-template-${id}`}>
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
|
|
||||||
import { SshCaSection } from "./components";
|
import { SshCaSection, SshCertificatesSection } from "./components";
|
||||||
|
|
||||||
|
enum TabSections {
|
||||||
|
SshCa = "ssh-certificate-authorities",
|
||||||
|
SshCertificates = "ssh-certificates"
|
||||||
|
}
|
||||||
|
|
||||||
export const SshPage = withPermission(
|
export const SshPage = withPermission(
|
||||||
() => {
|
() => {
|
||||||
@@ -9,7 +17,34 @@ export const SshPage = withPermission(
|
|||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6">
|
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6">
|
||||||
<p className="mr-4 mb-4 text-3xl font-semibold text-white">SSH</p>
|
<p className="mr-4 mb-4 text-3xl font-semibold text-white">SSH</p>
|
||||||
<SshCaSection />
|
<Tabs defaultValue={TabSections.SshCertificates}>
|
||||||
|
<TabList>
|
||||||
|
<Tab value={TabSections.SshCertificates}>SSH Certificates</Tab>
|
||||||
|
<Tab value={TabSections.SshCa}>Certificate Authorities</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel value={TabSections.SshCertificates}>
|
||||||
|
<motion.div
|
||||||
|
key="panel-ssh-certificate-s"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
>
|
||||||
|
<SshCertificatesSection />
|
||||||
|
</motion.div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.SshCa}>
|
||||||
|
<motion.div
|
||||||
|
key="panel-ssh-certificate-authorities"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
>
|
||||||
|
<SshCaSection />
|
||||||
|
</motion.div>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { SshCertificateModal } from "../../SshCaPage/components/SshCertificateModal";
|
||||||
|
import { SshCertificatesTable } from "./SshCertificatesTable";
|
||||||
|
|
||||||
|
export const SshCertificatesSection = () => {
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["sshCertificate"] as const);
|
||||||
|
return (
|
||||||
|
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex justify-between">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">Certificates</p>
|
||||||
|
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.SshCertificates}>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={() => handlePopUpOpen("sshCertificate")}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
>
|
||||||
|
Request
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
</div>
|
||||||
|
<SshCertificatesTable />
|
||||||
|
<SshCertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { faCertificate } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
import {
|
||||||
|
EmptyState,
|
||||||
|
Pagination,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tr} from "@app/components/v2";
|
||||||
|
import { useOrganization } from "@app/context";
|
||||||
|
import { useListOrgSshCertificates } from "@app/hooks/api";
|
||||||
|
import { sshCertTypeToNameMap } from "@app/hooks/api/ssh-ca/constants";
|
||||||
|
|
||||||
|
const PER_PAGE_INIT = 25;
|
||||||
|
|
||||||
|
export const SshCertificatesTable = () => {
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
||||||
|
|
||||||
|
const { data, isLoading } = useListOrgSshCertificates({
|
||||||
|
orgId: currentOrg?.id ?? "",
|
||||||
|
offset: (page - 1) * perPage,
|
||||||
|
limit: perPage
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Serial Number</Th>
|
||||||
|
<Th>Certificate Type</Th>
|
||||||
|
<Th>Principals</Th>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isLoading && <TableSkeleton columns={4} innerKey="org-ssh-certificates" />}
|
||||||
|
{!isLoading &&
|
||||||
|
data?.certificates?.map((certificate) => {
|
||||||
|
return (
|
||||||
|
<Tr className="h-10" key={`certificate-${certificate.id}`}>
|
||||||
|
<Td>{certificate.serialNumber}</Td>
|
||||||
|
<Td>{sshCertTypeToNameMap[certificate.certType]}</Td>
|
||||||
|
<Td>{certificate.principals.join(", ")}</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
{!isLoading && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && (
|
||||||
|
<Pagination
|
||||||
|
count={data.totalCount}
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
onChangePage={(newPage) => setPage(newPage)}
|
||||||
|
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!isLoading && !data?.certificates?.length && (
|
||||||
|
<EmptyState title="No SSH certificates have been issued" icon={faCertificate} />
|
||||||
|
)}
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { SshCaSection } from "./SshCaSection";
|
export { SshCaSection } from "./SshCaSection";
|
||||||
|
export { SshCertificatesSection } from "./SshCertificatesSection";
|
||||||
|
|||||||
Reference in New Issue
Block a user