feat: initialize azure-acme-provisioner project with core functionality

- Add package.json for project metadata and dependencies
- Implement CLI in src/cli.ts for managing SSL/TLS certificates
- Create Azure Functions host configuration in src/function/host.json
- Set up timer function in src/function/index.ts for scheduled certificate management
- Define configuration loading and error handling in src/lib/config.ts
- Implement DNS zone scanning and challenge management in src/lib/dns.ts
- Develop ACME client for certificate issuance in src/lib/acme.ts
- Create KeyVault store for managing secrets and certificates in src/lib/keyvault.ts
- Implement provisioning logic in src/lib/provisioner.ts for issuing and renewing certificates
- Add TypeScript configuration files for building the project
This commit is contained in:
2026-05-21 13:40:40 +02:00
parent c2af853df6
commit e7098015de
18 changed files with 2560 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
import { TokenCredential } from '@azure/identity';
import {
CertificateClient,
ImportCertificateOptions,
KeyVaultCertificateWithPolicy,
} from '@azure/keyvault-certificates';
import { SecretClient } from '@azure/keyvault-secrets';
export class KeyVaultStore {
private readonly secretClient: SecretClient;
private readonly certClient: CertificateClient;
constructor(credential: TokenCredential, keyVaultUrl: string) {
this.secretClient = new SecretClient(keyVaultUrl, credential);
this.certClient = new CertificateClient(keyVaultUrl, credential);
}
async getSecret(name: string): Promise<string | undefined> {
try {
const secret = await this.secretClient.getSecret(name);
return secret.value;
} catch (err: unknown) {
if (isNotFound(err)) return undefined;
throw err;
}
}
async setSecret(name: string, value: string): Promise<void> {
await this.secretClient.setSecret(name, value);
}
async getCertificate(name: string): Promise<KeyVaultCertificateWithPolicy | undefined> {
try {
return await this.certClient.getCertificate(name);
} catch (err: unknown) {
if (isNotFound(err)) return undefined;
throw err;
}
}
async certificateExpiresWithin(name: string, days: number): Promise<boolean | 'missing'> {
const cert = await this.getCertificate(name);
if (!cert) return 'missing';
const expiresOn = cert.properties.expiresOn;
if (!expiresOn) return false;
const thresholdMs = days * 24 * 60 * 60 * 1000;
return expiresOn.getTime() - Date.now() <= thresholdMs;
}
async importCertificate(name: string, pemBundle: string): Promise<void> {
const options: ImportCertificateOptions = {
policy: {
contentType: 'application/x-pem-file',
issuerName: 'Unknown',
subject: 'CN=unknown',
},
};
await this.certClient.importCertificate(name, Buffer.from(pemBundle), options);
}
}
function isNotFound(err: unknown): boolean {
return (
typeof err === 'object' &&
err !== null &&
'statusCode' in err &&
(err as { statusCode: number }).statusCode === 404
);
}