feat: add assign-role command to manage Key Vault roles for domain certificates

This commit is contained in:
2026-05-22 12:11:58 +02:00
parent 40ec41da28
commit d433569bab
4 changed files with 65 additions and 4 deletions
+2 -1
View File
@@ -44,7 +44,8 @@ Commands:
scan List all domains tagged for ACME management
status Show certificate expiry status for all managed domains
renew Force-renew a certificate for a specific domain
download Download the PEM bundle for a domain from Key Vault
download Download the PEM bundle for a domain from Key Vault
assign-role Assign Key Vault roles to a principal for a domain certificate
Common options:
--keyvault-url <url> Azure KeyVault URL
+19 -2
View File
@@ -1,14 +1,15 @@
{
"name": "azure-acme-provisioner",
"version": "0.3.4",
"version": "0.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "azure-acme-provisioner",
"version": "0.3.4",
"version": "0.4.0",
"license": "MIT",
"dependencies": {
"@azure/arm-authorization": "^9.0.0",
"@azure/arm-dns": "^5.1.0",
"@azure/functions": "^4.14.0",
"@azure/identity": "^4.13.1",
@@ -73,6 +74,22 @@
"node": ">=12.0.0"
}
},
"node_modules/@azure/arm-authorization": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@azure/arm-authorization/-/arm-authorization-9.0.0.tgz",
"integrity": "sha512-GdiCA8IA1gO+qcCbFEPj+iLC4+3ByjfKzmeAnkP7MdlL84Yo30Huo/EwbZzwRjYybXYUBuFxGPBB+yeTT4Ebxg==",
"license": "MIT",
"dependencies": {
"@azure/core-auth": "^1.3.0",
"@azure/core-client": "^1.7.0",
"@azure/core-paging": "^1.2.0",
"@azure/core-rest-pipeline": "^1.8.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@azure/arm-dns": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@azure/arm-dns/-/arm-dns-5.1.0.tgz",
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "azure-acme-provisioner",
"version": "0.3.4",
"version": "0.4.0",
"author": {
"name": "Sławomir Koszewski",
"url": "https://github.com/skoszewski"
@@ -37,6 +37,7 @@
"start:function": "func start"
},
"dependencies": {
"@azure/arm-authorization": "^9.0.0",
"@azure/arm-dns": "^5.1.0",
"@azure/functions": "^4.14.0",
"@azure/identity": "^4.13.1",
+42
View File
@@ -1,9 +1,17 @@
#!/usr/bin/env node
import { randomUUID } from 'node:crypto';
import { writeFileSync } from 'node:fs';
import { AuthorizationManagementClient } from '@azure/arm-authorization';
import { DefaultAzureCredential } from '@azure/identity';
import { Command } from 'commander';
import { loadConfig } from './lib/config.js';
import { domainToCertName, Provisioner } from './lib/provisioner.js';
const ROLE_IDS = {
'Key Vault Certificate User': 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba',
'Key Vault Secrets User': '4633458b-17de-408a-b874-0445c86b69e6',
} as const;
const program = new Command();
program
@@ -31,6 +39,7 @@ function applyOverrides(options: Record<string, unknown>): void {
const sharedOptions = (cmd: Command): Command =>
cmd
.option('--keyvault-url <url>', 'Azure KeyVault URL')
.option('--keyvault-resource-group <rg>', 'Resource group containing the Key Vault')
.option('--subscription-id <id>', 'Azure subscription ID')
.option('--resource-group <rg>', 'Resource group to scan (repeatable)', collect, [])
.option('--dns-zone <zone>', 'Restrict to specific DNS zone (repeatable)', collect, [])
@@ -120,6 +129,39 @@ sharedOptions(
if (result.errors.length > 0) process.exit(1);
});
sharedOptions(
program
.command('assign-role <domain>')
.description('Assign Key Vault Certificate User and Secrets User roles to a principal for a domain certificate')
.requiredOption('--principal-id <id>', 'Azure principal ID to assign roles to')
).action(async (domain: string, options: Record<string, unknown>) => {
applyOverrides(options);
const config = loadConfig();
if (!config.subscriptionId) throw new Error('--subscription-id is required');
if (!config.keyVaultUrl) throw new Error('--keyvault-url is required');
const kvRg = options['keyvaultResourceGroup'];
if (!kvRg) throw new Error('--keyvault-resource-group is required');
const sub = config.subscriptionId;
const principalId = String(options['principalId']);
const vaultName = new URL(config.keyVaultUrl).hostname.split('.')[0];
const certName = domainToCertName(domain);
const vaultBase = `/subscriptions/${sub}/resourceGroups/${kvRg}/providers/Microsoft.KeyVault/vaults/${vaultName}`;
const credential = new DefaultAzureCredential();
const authClient = new AuthorizationManagementClient(credential, sub);
const assignments = [
{ role: 'Key Vault Certificate User' as const, scope: `${vaultBase}/certificates/${certName}` },
{ role: 'Key Vault Secrets User' as const, scope: `${vaultBase}/secrets/${certName}` },
];
for (const { role, scope } of assignments) {
const roleDefinitionId = `/subscriptions/${sub}/providers/Microsoft.Authorization/roleDefinitions/${ROLE_IDS[role]}`;
await authClient.roleAssignments.create(scope, randomUUID(), { roleDefinitionId, principalId });
console.log(`Assigned '${role}' to ${principalId} on ${scope}`);
}
});
sharedOptions(
program
.command('download <domain>')