feat: add assign-role command to manage Key Vault roles for domain certificates
This commit is contained in:
@@ -45,6 +45,7 @@ Commands:
|
|||||||
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
|
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:
|
Common options:
|
||||||
--keyvault-url <url> Azure KeyVault URL
|
--keyvault-url <url> Azure KeyVault URL
|
||||||
|
|||||||
Generated
+19
-2
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "azure-acme-provisioner",
|
"name": "azure-acme-provisioner",
|
||||||
"version": "0.3.4",
|
"version": "0.4.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "azure-acme-provisioner",
|
"name": "azure-acme-provisioner",
|
||||||
"version": "0.3.4",
|
"version": "0.4.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/arm-authorization": "^9.0.0",
|
||||||
"@azure/arm-dns": "^5.1.0",
|
"@azure/arm-dns": "^5.1.0",
|
||||||
"@azure/functions": "^4.14.0",
|
"@azure/functions": "^4.14.0",
|
||||||
"@azure/identity": "^4.13.1",
|
"@azure/identity": "^4.13.1",
|
||||||
@@ -73,6 +74,22 @@
|
|||||||
"node": ">=12.0.0"
|
"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": {
|
"node_modules/@azure/arm-dns": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@azure/arm-dns/-/arm-dns-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@azure/arm-dns/-/arm-dns-5.1.0.tgz",
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "azure-acme-provisioner",
|
"name": "azure-acme-provisioner",
|
||||||
"version": "0.3.4",
|
"version": "0.4.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Sławomir Koszewski",
|
"name": "Sławomir Koszewski",
|
||||||
"url": "https://github.com/skoszewski"
|
"url": "https://github.com/skoszewski"
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
"start:function": "func start"
|
"start:function": "func start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/arm-authorization": "^9.0.0",
|
||||||
"@azure/arm-dns": "^5.1.0",
|
"@azure/arm-dns": "^5.1.0",
|
||||||
"@azure/functions": "^4.14.0",
|
"@azure/functions": "^4.14.0",
|
||||||
"@azure/identity": "^4.13.1",
|
"@azure/identity": "^4.13.1",
|
||||||
|
|||||||
+42
@@ -1,9 +1,17 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
import { writeFileSync } from 'node:fs';
|
import { writeFileSync } from 'node:fs';
|
||||||
|
import { AuthorizationManagementClient } from '@azure/arm-authorization';
|
||||||
|
import { DefaultAzureCredential } from '@azure/identity';
|
||||||
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';
|
||||||
|
|
||||||
|
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();
|
const program = new Command();
|
||||||
|
|
||||||
program
|
program
|
||||||
@@ -31,6 +39,7 @@ function applyOverrides(options: Record<string, unknown>): void {
|
|||||||
const sharedOptions = (cmd: Command): Command =>
|
const sharedOptions = (cmd: Command): Command =>
|
||||||
cmd
|
cmd
|
||||||
.option('--keyvault-url <url>', 'Azure KeyVault URL')
|
.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('--subscription-id <id>', 'Azure subscription ID')
|
||||||
.option('--resource-group <rg>', 'Resource group to scan (repeatable)', collect, [])
|
.option('--resource-group <rg>', 'Resource group to scan (repeatable)', collect, [])
|
||||||
.option('--dns-zone <zone>', 'Restrict to specific DNS zone (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);
|
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(
|
sharedOptions(
|
||||||
program
|
program
|
||||||
.command('download <domain>')
|
.command('download <domain>')
|
||||||
|
|||||||
Reference in New Issue
Block a user