feat(certificates): create certificates in a remote server

This commit is contained in:
Mauricio Siu
2024-10-12 19:09:50 -06:00
parent 339697437a
commit 6dd6b636e5
10 changed files with 4104 additions and 42 deletions

View File

@@ -8,6 +8,7 @@ import { registry } from "./registry";
import { certificateType } from "./shared";
import { sshKeys } from "./ssh-key";
import { users } from "./user";
import { certificates } from "./certificate";
export const admins = pgTable("admin", {
adminId: text("adminId")
@@ -37,6 +38,7 @@ export const adminsRelations = relations(admins, ({ one, many }) => ({
users: many(users),
registry: many(registry),
sshKeys: many(sshKeys),
certificates: many(certificates),
}));
const createSchema = createInsertSchema(admins, {

View File

@@ -3,6 +3,9 @@ import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { generateAppName } from "./utils";
import { admins } from "./admin";
import { server } from "./server";
import { relations } from "drizzle-orm";
export const certificates = pgTable("certificate", {
certificateId: text("certificateId")
@@ -17,13 +20,34 @@ export const certificates = pgTable("certificate", {
.$defaultFn(() => generateAppName("certificate"))
.unique(),
autoRenew: boolean("autoRenew"),
adminId: text("adminId").references(() => admins.adminId, {
onDelete: "cascade",
}),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
});
export const certificatesRelations = relations(
certificates,
({ one, many }) => ({
server: one(server, {
fields: [certificates.serverId],
references: [server.serverId],
}),
admin: one(admins, {
fields: [certificates.adminId],
references: [admins.adminId],
}),
}),
);
export const apiCreateCertificate = createInsertSchema(certificates, {
name: z.string().min(1),
certificateData: z.string().min(1),
privateKey: z.string().min(1),
autoRenew: z.boolean().optional(),
serverId: z.string().optional(),
});
export const apiFindCertificate = z.object({

View File

@@ -15,6 +15,7 @@ import { postgres } from "./postgres";
import { redis } from "./redis";
import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils";
import { certificates } from "./certificate";
export const server = pgTable("server", {
serverId: text("serverId")
@@ -58,6 +59,7 @@ export const serverRelations = relations(server, ({ one, many }) => ({
mongo: many(mongo),
mysql: many(mysql),
postgres: many(postgres),
certificates: many(certificates),
}));
const createSchema = createInsertSchema(server, {

View File

@@ -8,6 +8,8 @@ import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { dump } from "js-yaml";
import type { z } from "zod";
import { encodeBase64 } from "../utils/docker/utils";
import { execAsyncRemote } from "../utils/process/execAsync";
export type Certificate = typeof certificates.$inferSelect;
@@ -28,11 +30,13 @@ export const findCertificateById = async (certificateId: string) => {
export const createCertificate = async (
certificateData: z.infer<typeof apiCreateCertificate>,
adminId: string,
) => {
const certificate = await db
.insert(certificates)
.values({
...certificateData,
adminId: adminId,
})
.returning();
@@ -46,15 +50,21 @@ export const createCertificate = async (
const cer = certificate[0];
createCertificateFiles(cer);
return cer;
};
export const removeCertificateById = async (certificateId: string) => {
const { CERTIFICATES_PATH } = paths();
const certificate = await findCertificateById(certificateId);
const { CERTIFICATES_PATH } = paths(!!certificate.serverId);
const certDir = path.join(CERTIFICATES_PATH, certificate.certificatePath);
await removeDirectoryIfExistsContent(certDir);
if (certificate.serverId) {
await execAsyncRemote(certificate.serverId, `rm -rf ${certDir}`);
} else {
await removeDirectoryIfExistsContent(certDir);
}
const result = await db
.delete(certificates)
.where(eq(certificates.certificateId, certificateId))
@@ -70,27 +80,14 @@ export const removeCertificateById = async (certificateId: string) => {
return result;
};
export const findCertificates = async () => {
return await db.query.certificates.findMany();
};
const createCertificateFiles = (certificate: Certificate) => {
const { CERTIFICATES_PATH } = paths();
const dockerPath = "/etc/traefik";
const createCertificateFiles = async (certificate: Certificate) => {
const { CERTIFICATES_PATH } = paths(!!certificate.serverId);
const certDir = path.join(CERTIFICATES_PATH, certificate.certificatePath);
const crtPath = path.join(certDir, "chain.crt");
const keyPath = path.join(certDir, "privkey.key");
const chainPath = path.join(dockerPath, certDir, "chain.crt");
const keyPathDocker = path.join(dockerPath, certDir, "privkey.key");
if (!fs.existsSync(certDir)) {
fs.mkdirSync(certDir, { recursive: true });
}
fs.writeFileSync(crtPath, certificate.certificateData);
fs.writeFileSync(keyPath, certificate.privateKey);
const chainPath = path.join(certDir, "chain.crt");
const keyPathDocker = path.join(certDir, "privkey.key");
const traefikConfig = {
tls: {
certificates: [
@@ -101,8 +98,28 @@ const createCertificateFiles = (certificate: Certificate) => {
],
},
};
const yamlConfig = dump(traefikConfig);
const configFile = path.join(certDir, "certificate.yml");
fs.writeFileSync(configFile, yamlConfig);
if (certificate.serverId) {
const certificateData = encodeBase64(certificate.certificateData);
const privateKey = encodeBase64(certificate.privateKey);
const command = `
mkdir -p ${certDir};
echo "${certificateData}" | base64 -d > "${crtPath}";
echo "${privateKey}" | base64 -d > "${keyPath}";
echo "${yamlConfig}" > "${configFile}";
`;
await execAsyncRemote(certificate.serverId, command);
} else {
if (!fs.existsSync(certDir)) {
fs.mkdirSync(certDir, { recursive: true });
}
fs.writeFileSync(crtPath, certificate.certificateData);
fs.writeFileSync(keyPath, certificate.privateKey);
fs.writeFileSync(configFile, yamlConfig);
}
};