Files
onebox/ts/classes/certmanager.ts
Juergen Kunz 8ebd677478 feat: Implement platform service providers for MinIO and MongoDB
- Added base interface and abstract class for platform service providers.
- Created MinIOProvider class for S3-compatible storage with deployment, provisioning, and deprovisioning functionalities.
- Implemented MongoDBProvider class for MongoDB service with similar capabilities.
- Introduced error handling utilities for better error management.
- Developed TokensComponent for managing registry tokens in the UI, including creation, deletion, and display of tokens.
2025-11-25 04:20:19 +00:00

191 lines
5.6 KiB
TypeScript

/**
* SQLite-based Certificate Manager for SmartACME
*
* Implements ICertManager interface to store SSL certificates in SQLite database
* and write PEM files to filesystem for use by the reverse proxy.
*/
import * as plugins from '../plugins.ts';
import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
import { OneboxDatabase } from './database.ts';
export class SqliteCertManager implements plugins.smartacme.ICertManager {
private database: OneboxDatabase;
private certBasePath: string;
constructor(database: OneboxDatabase, certBasePath = './.nogit/ssl/live') {
this.database = database;
this.certBasePath = certBasePath;
}
/**
* Initialize the certificate manager
*/
async init(): Promise<void> {
try {
// Ensure certificate directory exists
await Deno.mkdir(this.certBasePath, { recursive: true });
logger.info(`Certificate manager initialized (path: ${this.certBasePath})`);
} catch (error) {
logger.error(`Failed to initialize certificate manager: ${getErrorMessage(error)}`);
throw error;
}
}
/**
* Retrieve a certificate by domain name
*/
async retrieveCertificate(domainName: string): Promise<plugins.smartacme.Cert | null> {
try {
const dbCert = this.database.getSSLCertificate(domainName);
if (!dbCert) {
return null;
}
// Convert database format to SmartacmeCert format
const cert = new plugins.smartacme.Cert({
id: dbCert.id?.toString() || domainName,
domainName: dbCert.domain,
created: dbCert.createdAt,
privateKey: await this.readPemFile(dbCert.keyPath),
publicKey: await this.readPemFile(dbCert.fullChainPath), // Full chain as public key
csr: '', // CSR not stored separately
validUntil: dbCert.expiryDate,
});
return cert;
} catch (error) {
logger.warn(`Failed to retrieve certificate for ${domainName}: ${getErrorMessage(error)}`);
return null;
}
}
/**
* Store a certificate
*/
async storeCertificate(cert: plugins.smartacme.Cert): Promise<void> {
try {
const domain = cert.domainName;
const domainPath = `${this.certBasePath}/${domain}`;
// Create domain-specific directory
await Deno.mkdir(domainPath, { recursive: true });
// Write PEM files
const keyPath = `${domainPath}/privkey.pem`;
const certPath = `${domainPath}/cert.pem`;
const fullChainPath = `${domainPath}/fullchain.pem`;
await Deno.writeTextFile(keyPath, cert.privateKey);
await Deno.writeTextFile(fullChainPath, cert.publicKey);
// Extract certificate from full chain (first certificate in the chain)
const certOnly = this.extractCertFromChain(cert.publicKey);
await Deno.writeTextFile(certPath, certOnly);
// Store/update in database
const existing = this.database.getSSLCertificate(domain);
if (existing) {
this.database.updateSSLCertificate(domain, {
certPath,
keyPath,
fullChainPath,
expiryDate: cert.validUntil,
updatedAt: Date.now(),
});
} else {
await this.database.createSSLCertificate({
domain,
certPath,
keyPath,
fullChainPath,
expiryDate: cert.validUntil,
issuer: 'Let\'s Encrypt',
createdAt: cert.created,
updatedAt: Date.now(),
});
}
logger.success(`Certificate stored for ${domain}`);
} catch (error) {
logger.error(`Failed to store certificate for ${cert.domainName}: ${getErrorMessage(error)}`);
throw error;
}
}
/**
* Delete a certificate
*/
async deleteCertificate(domainName: string): Promise<void> {
try {
const dbCert = this.database.getSSLCertificate(domainName);
if (dbCert) {
// Delete PEM files
const domainPath = `${this.certBasePath}/${domainName}`;
try {
await Deno.remove(domainPath, { recursive: true });
} catch (error) {
logger.warn(`Failed to delete PEM files for ${domainName}: ${getErrorMessage(error)}`);
}
// Delete from database
this.database.deleteSSLCertificate(domainName);
logger.info(`Certificate deleted for ${domainName}`);
}
} catch (error) {
logger.error(`Failed to delete certificate for ${domainName}: ${getErrorMessage(error)}`);
throw error;
}
}
/**
* Close the certificate manager
*/
async close(): Promise<void> {
// SQLite database is managed by OneboxDatabase, nothing to close here
logger.info('Certificate manager closed');
}
/**
* Wipe all certificates (for testing)
*/
async wipe(): Promise<void> {
try {
const certs = this.database.getAllSSLCertificates();
for (const cert of certs) {
await this.deleteCertificate(cert.domain);
}
logger.warn('All certificates wiped');
} catch (error) {
logger.error(`Failed to wipe certificates: ${getErrorMessage(error)}`);
throw error;
}
}
/**
* Read PEM file from filesystem
*/
private async readPemFile(path: string): Promise<string> {
try {
return await Deno.readTextFile(path);
} catch (error) {
throw new Error(`Failed to read PEM file ${path}: ${getErrorMessage(error)}`);
}
}
/**
* Extract the first certificate from a PEM chain
*/
private extractCertFromChain(fullChain: string): string {
const certMatch = fullChain.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/);
return certMatch ? certMatch[0] : fullChain;
}
}