Files
smartacme/readme.md

300 lines
10 KiB
Markdown
Raw Permalink Normal View History

2023-07-21 18:49:18 +02:00
# @push.rocks/smartacme
A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power. 🔒
2020-05-17 16:21:25 +00:00
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
## Install
2024-04-14 17:18:13 +02:00
```bash
pnpm add @push.rocks/smartacme
2024-04-14 17:18:13 +02:00
```
Ensure your project uses TypeScript and ECMAScript Modules (ESM).
## Usage
`@push.rocks/smartacme` automates the full ACME certificate lifecycle — obtaining, renewing, and storing SSL/TLS certificates from Let's Encrypt. It supports pluggable challenge handlers (DNS-01, HTTP-01) and pluggable certificate storage backends (MongoDB, in-memory, or your own).
### Quick Start
```typescript
import { SmartAcme, certmanagers, handlers } from '@push.rocks/smartacme';
import * as cloudflare from '@apiclient.xyz/cloudflare';
// 1. Set up a certificate manager (MongoDB or in-memory)
const certManager = new certmanagers.MongoCertManager({
mongoDbUrl: 'mongodb://localhost:27017',
mongoDbName: 'myapp',
mongoDbPass: 'secret',
});
// 2. Set up challenge handlers
const cfAccount = new cloudflare.CloudflareAccount('YOUR_CF_API_TOKEN');
const dnsHandler = new handlers.Dns01Handler(cfAccount);
// 3. Create and start SmartAcme
const smartAcme = new SmartAcme({
accountEmail: 'admin@example.com',
certManager,
environment: 'production', // or 'integration' for staging
challengeHandlers: [dnsHandler],
});
await smartAcme.start();
// 4. Get a certificate
const cert = await smartAcme.getCertificateForDomain('example.com');
console.log(cert.publicKey); // PEM certificate
console.log(cert.privateKey); // PEM private key
// 5. Clean up
await smartAcme.stop();
2024-04-14 17:18:13 +02:00
```
### SmartAcme Options
```typescript
interface ISmartAcmeOptions {
accountEmail: string; // ACME account email
accountPrivateKey?: string; // Optional account key (auto-generated if omitted)
certManager: ICertManager; // Certificate storage backend
environment: 'production' | 'integration'; // LetsEncrypt environment
challengeHandlers: IChallengeHandler[]; // At least one handler required
challengePriority?: string[]; // e.g. ['dns-01', 'http-01']
retryOptions?: { // Optional retry/backoff config
retries?: number;
factor?: number;
minTimeoutMs?: number;
maxTimeoutMs?: number;
};
}
```
2020-05-17 16:21:25 +00:00
### Getting Certificates
```typescript
// Standard certificate for a single domain
const cert = await smartAcme.getCertificateForDomain('example.com');
// Include wildcard certificate (requires DNS-01 handler)
const certWithWildcard = await smartAcme.getCertificateForDomain('example.com', {
includeWildcard: true,
});
2020-05-17 16:21:25 +00:00
// Request wildcard only
const wildcardCert = await smartAcme.getCertificateForDomain('*.example.com');
```
2024-04-14 17:18:13 +02:00
Certificates are automatically cached and reused when still valid. Renewal happens automatically when a certificate is within 10 days of expiration.
### Certificate Object
The returned `SmartacmeCert` object has these properties:
2024-04-14 17:18:13 +02:00
| Property | Type | Description |
|-------------|----------|--------------------------------------|
| `id` | `string` | Unique certificate identifier |
| `domainName`| `string` | Domain the cert is issued for |
| `publicKey` | `string` | PEM-encoded certificate |
| `privateKey`| `string` | PEM-encoded private key |
| `csr` | `string` | Certificate Signing Request |
| `created` | `number` | Timestamp of creation |
| `validUntil`| `number` | Timestamp of expiration |
2024-04-14 17:18:13 +02:00
## Certificate Managers
2024-04-14 17:18:13 +02:00
SmartAcme uses the `ICertManager` interface for pluggable certificate storage.
2024-04-14 17:18:13 +02:00
### MongoCertManager
Persistent storage backed by MongoDB using `@push.rocks/smartdata`:
2024-04-14 17:18:13 +02:00
```typescript
import { certmanagers } from '@push.rocks/smartacme';
const certManager = new certmanagers.MongoCertManager({
mongoDbUrl: 'mongodb://localhost:27017',
mongoDbName: 'myapp',
mongoDbPass: 'secret',
2024-04-14 17:18:13 +02:00
});
```
### MemoryCertManager
2024-04-14 17:18:13 +02:00
In-memory storage, ideal for testing or ephemeral workloads:
2024-04-14 17:18:13 +02:00
```typescript
import { certmanagers } from '@push.rocks/smartacme';
const certManager = new certmanagers.MemoryCertManager();
2024-04-14 17:18:13 +02:00
```
### Custom Certificate Manager
2024-04-14 17:18:13 +02:00
Implement the `ICertManager` interface for your own storage backend:
2024-04-14 17:18:13 +02:00
```typescript
import type { ICertManager } from '@push.rocks/smartacme';
import { Cert } from '@push.rocks/smartacme';
class RedisCertManager implements ICertManager {
async init(): Promise<void> { /* connect */ }
async retrieveCertificate(domainName: string): Promise<Cert | null> { /* lookup */ }
async storeCertificate(cert: Cert): Promise<void> { /* save */ }
async deleteCertificate(domainName: string): Promise<void> { /* remove */ }
async close(): Promise<void> { /* disconnect */ }
async wipe(): Promise<void> { /* clear all */ }
}
2024-04-14 17:18:13 +02:00
```
## Challenge Handlers
2024-04-14 17:18:13 +02:00
SmartAcme ships with three built-in ACME challenge handlers. All implement `IChallengeHandler<T>`.
### 🌐 Dns01Handler
2024-04-14 17:18:13 +02:00
Uses Cloudflare (or any `IConvenientDnsProvider`) to set and remove DNS TXT records for `dns-01` challenges:
2024-04-14 17:18:13 +02:00
```typescript
import { handlers } from '@push.rocks/smartacme';
import * as cloudflare from '@apiclient.xyz/cloudflare';
const cfAccount = new cloudflare.CloudflareAccount('YOUR_CF_TOKEN');
const dnsHandler = new handlers.Dns01Handler(cfAccount);
2020-05-17 16:21:25 +00:00
```
DNS-01 is required for wildcard certificates and works regardless of server accessibility.
### 📁 Http01Webroot
Writes challenge response files to a filesystem webroot for `http-01` validation:
```typescript
import { handlers } from '@push.rocks/smartacme';
const httpHandler = new handlers.Http01Webroot({
webroot: '/var/www/html',
});
```
The handler writes to `<webroot>/.well-known/acme-challenge/<token>` and cleans up after validation.
2024-04-14 17:18:13 +02:00
### 🧠 Http01MemoryHandler
2024-04-14 17:18:13 +02:00
In-memory HTTP-01 handler — stores challenge tokens in memory and serves them via `handleRequest()`:
```typescript
import { handlers } from '@push.rocks/smartacme';
const memHandler = new handlers.Http01MemoryHandler();
// Integrate with any HTTP server (Express, Koa, raw http, etc.)
app.use((req, res, next) => memHandler.handleRequest(req, res, next));
```
Perfect for serverless or container environments where filesystem access is limited.
### Custom Challenge Handler
Implement `IChallengeHandler<T>` for custom challenge types:
```typescript
import type { IChallengeHandler } from '@push.rocks/smartacme';
interface MyChallenge {
type: string;
token: string;
keyAuthorization: string;
}
2024-04-14 17:18:13 +02:00
class MyHandler implements IChallengeHandler<MyChallenge> {
getSupportedTypes(): string[] { return ['http-01']; }
async prepare(ch: MyChallenge): Promise<void> { /* ... */ }
async cleanup(ch: MyChallenge): Promise<void> { /* ... */ }
async checkWetherDomainIsSupported(domain: string): Promise<boolean> { return true; }
}
```
## Domain Matching
SmartAcme automatically maps subdomains to their base domain for certificate lookups:
```typescript
// subdomain.example.com → certificate for example.com
// *.example.com → certificate for example.com
// a.b.example.com → not supported (4+ level domains)
```
## Environment
- **`production`** — Uses LetsEncrypt production servers. Rate limits apply.
- **`integration`** — Uses LetsEncrypt staging servers. No rate limits, but certificates are not trusted by browsers. Use for testing.
## Complete Example with HTTP-01
```typescript
import { SmartAcme, certmanagers, handlers } from '@push.rocks/smartacme';
import * as http from 'http';
// In-memory handler for HTTP-01 challenges
const memHandler = new handlers.Http01MemoryHandler();
// Create HTTP server that serves ACME challenges
const server = http.createServer((req, res) => {
memHandler.handleRequest(req, res, () => {
res.statusCode = 200;
res.end('OK');
});
});
server.listen(80);
// Set up SmartAcme with in-memory storage and HTTP-01
const smartAcme = new SmartAcme({
accountEmail: 'admin@example.com',
certManager: new certmanagers.MemoryCertManager(),
environment: 'production',
challengeHandlers: [memHandler],
challengePriority: ['http-01'],
});
await smartAcme.start();
const cert = await smartAcme.getCertificateForDomain('example.com');
// Use cert.publicKey and cert.privateKey with your HTTPS server
await smartAcme.stop();
server.close();
```
## Testing
```bash
pnpm test
```
Tests use `@git.zone/tstest` with the tapbundle assertion library.
2024-04-14 17:18:13 +02:00
## License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
2024-04-14 17:18:13 +02:00
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
2024-04-14 17:18:13 +02:00
### Company Information
2020-05-17 16:21:25 +00:00
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
2020-05-17 16:21:25 +00:00
For any legal inquiries or further information, please contact us via email at hello@task.vc.
2020-05-17 16:21:25 +00:00
2024-04-14 17:18:13 +02:00
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.