feat: add download command to retrieve PEM bundle from Key Vault

This commit is contained in:
2026-05-21 23:47:49 +02:00
parent a92bdabac3
commit 72e47e2a9d
3 changed files with 40 additions and 1 deletions
+13
View File
@@ -44,6 +44,7 @@ Commands:
scan List all domains tagged for ACME management scan List all domains tagged for ACME management
status Show certificate expiry status for all managed domains status Show certificate expiry status for all managed domains
renew Force-renew a certificate for a specific domain renew Force-renew a certificate for a specific domain
download Download the PEM bundle for a domain from Key Vault
Common options: Common options:
--keyvault-url <url> Azure KeyVault URL --keyvault-url <url> Azure KeyVault URL
@@ -77,6 +78,18 @@ azure-acme-provisioner run --http 8080
> **Note:** Binding port 80 requires root privileges or `CAP_NET_BIND_SERVICE`. When running in Docker, map the host port to the container: `-p 80:8080` and pass `--http 8080`. > **Note:** Binding port 80 requires root privileges or `CAP_NET_BIND_SERVICE`. When running in Docker, map the host port to the container: `-p 80:8080` and pass `--http 8080`.
### Downloading certificates
The `download` command fetches the PEM bundle (private key + certificate + chain) from Key Vault and writes it to stdout or a file:
```sh
# Print to stdout
azure-acme-provisioner download api.example.com
# Write to a file
azure-acme-provisioner download api.example.com --output api.example.com.pem
```
## Configuration ## Configuration
All configuration is via environment variables. CLI flags override env vars when both are provided. All configuration is via environment variables. CLI flags override env vars when both are provided.
+19
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
import { writeFileSync } from 'node:fs';
import { Command } from 'commander'; import { Command } from 'commander';
import { loadConfig } from './lib/config.js'; import { loadConfig } from './lib/config.js';
import { domainToCertName, Provisioner } from './lib/provisioner.js'; import { domainToCertName, Provisioner } from './lib/provisioner.js';
@@ -119,6 +120,24 @@ sharedOptions(
if (result.errors.length > 0) process.exit(1); if (result.errors.length > 0) process.exit(1);
}); });
sharedOptions(
program
.command('download <domain>')
.description('Download the PEM bundle (private key + certificate + chain) for a domain')
.option('--output <file>', 'Write to file instead of stdout')
).action(async (domain: string, options: Record<string, unknown>) => {
applyOverrides(options);
const config = loadConfig();
const provisioner = new Provisioner(config);
const pem = await provisioner.download(domain);
if (options['output']) {
writeFileSync(String(options['output']), pem, 'utf8');
console.log(`Certificate written to ${options['output']}`);
} else {
process.stdout.write(pem);
}
});
program.parseAsync(process.argv).catch((err: unknown) => { program.parseAsync(process.argv).catch((err: unknown) => {
console.error(err instanceof Error ? err.message : String(err)); console.error(err instanceof Error ? err.message : String(err));
process.exit(1); process.exit(1);
+7
View File
@@ -131,6 +131,13 @@ export class Provisioner {
return result; return result;
} }
async download(domain: string): Promise<string> {
const certName = domainToCertName(domain);
const pem = await this.store.getSecret(certName);
if (!pem) throw new Error(`Certificate not found in KeyVault: ${certName}`);
return pem;
}
async scan(): Promise<DomainRecord[]> { async scan(): Promise<DomainRecord[]> {
return scanDnsZones(this.credential, this.config); return scanDnsZones(this.credential, this.config);
} }