@peterhauge/apiops-cli 0.1.3-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +135 -0
- package/dist/cli/extract-command.d.ts +12 -0
- package/dist/cli/extract-command.d.ts.map +1 -0
- package/dist/cli/extract-command.js +157 -0
- package/dist/cli/extract-command.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +74 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-command.d.ts +11 -0
- package/dist/cli/init-command.d.ts.map +1 -0
- package/dist/cli/init-command.js +87 -0
- package/dist/cli/init-command.js.map +1 -0
- package/dist/cli/publish-command.d.ts +12 -0
- package/dist/cli/publish-command.d.ts.map +1 -0
- package/dist/cli/publish-command.js +159 -0
- package/dist/cli/publish-command.js.map +1 -0
- package/dist/clients/apim-client.d.ts +110 -0
- package/dist/clients/apim-client.d.ts.map +1 -0
- package/dist/clients/apim-client.js +586 -0
- package/dist/clients/apim-client.js.map +1 -0
- package/dist/clients/artifact-store.d.ts +23 -0
- package/dist/clients/artifact-store.d.ts.map +1 -0
- package/dist/clients/artifact-store.js +188 -0
- package/dist/clients/artifact-store.js.map +1 -0
- package/dist/clients/iapim-client.d.ts +52 -0
- package/dist/clients/iapim-client.d.ts.map +1 -0
- package/dist/clients/iapim-client.js +6 -0
- package/dist/clients/iapim-client.js.map +1 -0
- package/dist/clients/iartifact-store.d.ts +50 -0
- package/dist/clients/iartifact-store.d.ts.map +1 -0
- package/dist/clients/iartifact-store.js +6 -0
- package/dist/clients/iartifact-store.js.map +1 -0
- package/dist/lib/auto-generated.d.ts +27 -0
- package/dist/lib/auto-generated.d.ts.map +1 -0
- package/dist/lib/auto-generated.js +34 -0
- package/dist/lib/auto-generated.js.map +1 -0
- package/dist/lib/cloud-config.d.ts +29 -0
- package/dist/lib/cloud-config.d.ts.map +1 -0
- package/dist/lib/cloud-config.js +60 -0
- package/dist/lib/cloud-config.js.map +1 -0
- package/dist/lib/config-loader.d.ts +21 -0
- package/dist/lib/config-loader.d.ts.map +1 -0
- package/dist/lib/config-loader.js +131 -0
- package/dist/lib/config-loader.js.map +1 -0
- package/dist/lib/dependency-graph.d.ts +43 -0
- package/dist/lib/dependency-graph.d.ts.map +1 -0
- package/dist/lib/dependency-graph.js +163 -0
- package/dist/lib/dependency-graph.js.map +1 -0
- package/dist/lib/exit-codes.d.ts +27 -0
- package/dist/lib/exit-codes.d.ts.map +1 -0
- package/dist/lib/exit-codes.js +33 -0
- package/dist/lib/exit-codes.js.map +1 -0
- package/dist/lib/logger.d.ts +39 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +128 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/parallel-runner.d.ts +38 -0
- package/dist/lib/parallel-runner.d.ts.map +1 -0
- package/dist/lib/parallel-runner.js +70 -0
- package/dist/lib/parallel-runner.js.map +1 -0
- package/dist/lib/resource-path.d.ts +205 -0
- package/dist/lib/resource-path.d.ts.map +1 -0
- package/dist/lib/resource-path.js +401 -0
- package/dist/lib/resource-path.js.map +1 -0
- package/dist/lib/resource-uri.d.ts +40 -0
- package/dist/lib/resource-uri.d.ts.map +1 -0
- package/dist/lib/resource-uri.js +86 -0
- package/dist/lib/resource-uri.js.map +1 -0
- package/dist/lib/user-agent.d.ts +2 -0
- package/dist/lib/user-agent.d.ts.map +1 -0
- package/dist/lib/user-agent.js +5 -0
- package/dist/lib/user-agent.js.map +1 -0
- package/dist/models/config.d.ts +83 -0
- package/dist/models/config.d.ts.map +1 -0
- package/dist/models/config.js +6 -0
- package/dist/models/config.js.map +1 -0
- package/dist/models/resource-types.d.ts +66 -0
- package/dist/models/resource-types.d.ts.map +1 -0
- package/dist/models/resource-types.js +243 -0
- package/dist/models/resource-types.js.map +1 -0
- package/dist/models/types.d.ts +47 -0
- package/dist/models/types.d.ts.map +1 -0
- package/dist/models/types.js +6 -0
- package/dist/models/types.js.map +1 -0
- package/dist/services/api-extractor.d.ts +36 -0
- package/dist/services/api-extractor.d.ts.map +1 -0
- package/dist/services/api-extractor.js +319 -0
- package/dist/services/api-extractor.js.map +1 -0
- package/dist/services/api-publisher.d.ts +18 -0
- package/dist/services/api-publisher.d.ts.map +1 -0
- package/dist/services/api-publisher.js +290 -0
- package/dist/services/api-publisher.js.map +1 -0
- package/dist/services/delete-unmatched-service.d.ts +17 -0
- package/dist/services/delete-unmatched-service.d.ts.map +1 -0
- package/dist/services/delete-unmatched-service.js +143 -0
- package/dist/services/delete-unmatched-service.js.map +1 -0
- package/dist/services/dry-run-reporter.d.ts +30 -0
- package/dist/services/dry-run-reporter.d.ts.map +1 -0
- package/dist/services/dry-run-reporter.js +111 -0
- package/dist/services/dry-run-reporter.js.map +1 -0
- package/dist/services/extract-service.d.ts +47 -0
- package/dist/services/extract-service.d.ts.map +1 -0
- package/dist/services/extract-service.js +374 -0
- package/dist/services/extract-service.js.map +1 -0
- package/dist/services/filter-service.d.ts +29 -0
- package/dist/services/filter-service.d.ts.map +1 -0
- package/dist/services/filter-service.js +143 -0
- package/dist/services/filter-service.js.map +1 -0
- package/dist/services/git-diff-service.d.ts +23 -0
- package/dist/services/git-diff-service.d.ts.map +1 -0
- package/dist/services/git-diff-service.js +135 -0
- package/dist/services/git-diff-service.js.map +1 -0
- package/dist/services/identity-guide-service.d.ts +11 -0
- package/dist/services/identity-guide-service.d.ts.map +1 -0
- package/dist/services/identity-guide-service.js +227 -0
- package/dist/services/identity-guide-service.js.map +1 -0
- package/dist/services/init-service.d.ts +16 -0
- package/dist/services/init-service.d.ts.map +1 -0
- package/dist/services/init-service.js +304 -0
- package/dist/services/init-service.js.map +1 -0
- package/dist/services/keyvault-checker.d.ts +58 -0
- package/dist/services/keyvault-checker.d.ts.map +1 -0
- package/dist/services/keyvault-checker.js +390 -0
- package/dist/services/keyvault-checker.js.map +1 -0
- package/dist/services/override-merger.d.ts +20 -0
- package/dist/services/override-merger.d.ts.map +1 -0
- package/dist/services/override-merger.js +102 -0
- package/dist/services/override-merger.js.map +1 -0
- package/dist/services/product-extractor.d.ts +26 -0
- package/dist/services/product-extractor.d.ts.map +1 -0
- package/dist/services/product-extractor.js +141 -0
- package/dist/services/product-extractor.js.map +1 -0
- package/dist/services/product-publisher.d.ts +15 -0
- package/dist/services/product-publisher.d.ts.map +1 -0
- package/dist/services/product-publisher.js +113 -0
- package/dist/services/product-publisher.js.map +1 -0
- package/dist/services/prompt-service.d.ts +13 -0
- package/dist/services/prompt-service.d.ts.map +1 -0
- package/dist/services/prompt-service.js +69 -0
- package/dist/services/prompt-service.js.map +1 -0
- package/dist/services/publish-service.d.ts +31 -0
- package/dist/services/publish-service.d.ts.map +1 -0
- package/dist/services/publish-service.js +445 -0
- package/dist/services/publish-service.js.map +1 -0
- package/dist/services/resource-extractor.d.ts +52 -0
- package/dist/services/resource-extractor.d.ts.map +1 -0
- package/dist/services/resource-extractor.js +168 -0
- package/dist/services/resource-extractor.js.map +1 -0
- package/dist/services/resource-publisher.d.ts +23 -0
- package/dist/services/resource-publisher.d.ts.map +1 -0
- package/dist/services/resource-publisher.js +349 -0
- package/dist/services/resource-publisher.js.map +1 -0
- package/dist/services/secret-redactor.d.ts +20 -0
- package/dist/services/secret-redactor.d.ts.map +1 -0
- package/dist/services/secret-redactor.js +45 -0
- package/dist/services/secret-redactor.js.map +1 -0
- package/dist/services/transitive-resolver.d.ts +45 -0
- package/dist/services/transitive-resolver.d.ts.map +1 -0
- package/dist/services/transitive-resolver.js +177 -0
- package/dist/services/transitive-resolver.js.map +1 -0
- package/dist/services/workspace-extractor.d.ts +34 -0
- package/dist/services/workspace-extractor.d.ts.map +1 -0
- package/dist/services/workspace-extractor.js +120 -0
- package/dist/services/workspace-extractor.js.map +1 -0
- package/dist/templates/azure-devops/extract-pipeline.d.ts +9 -0
- package/dist/templates/azure-devops/extract-pipeline.d.ts.map +1 -0
- package/dist/templates/azure-devops/extract-pipeline.js +95 -0
- package/dist/templates/azure-devops/extract-pipeline.js.map +1 -0
- package/dist/templates/azure-devops/publish-pipeline.d.ts +10 -0
- package/dist/templates/azure-devops/publish-pipeline.d.ts.map +1 -0
- package/dist/templates/azure-devops/publish-pipeline.js +100 -0
- package/dist/templates/azure-devops/publish-pipeline.js.map +1 -0
- package/dist/templates/configs/filter-config.d.ts +6 -0
- package/dist/templates/configs/filter-config.d.ts.map +1 -0
- package/dist/templates/configs/filter-config.js +51 -0
- package/dist/templates/configs/filter-config.js.map +1 -0
- package/dist/templates/configs/override-config.d.ts +6 -0
- package/dist/templates/configs/override-config.d.ts.map +1 -0
- package/dist/templates/configs/override-config.js +45 -0
- package/dist/templates/configs/override-config.js.map +1 -0
- package/dist/templates/configs/package-json.d.ts +10 -0
- package/dist/templates/configs/package-json.d.ts.map +1 -0
- package/dist/templates/configs/package-json.js +19 -0
- package/dist/templates/configs/package-json.js.map +1 -0
- package/dist/templates/copilot/identity-setup-prompt.d.ts +13 -0
- package/dist/templates/copilot/identity-setup-prompt.d.ts.map +1 -0
- package/dist/templates/copilot/identity-setup-prompt.js +279 -0
- package/dist/templates/copilot/identity-setup-prompt.js.map +1 -0
- package/dist/templates/github-actions/extract-workflow.d.ts +9 -0
- package/dist/templates/github-actions/extract-workflow.d.ts.map +1 -0
- package/dist/templates/github-actions/extract-workflow.js +126 -0
- package/dist/templates/github-actions/extract-workflow.js.map +1 -0
- package/dist/templates/github-actions/publish-workflow.d.ts +10 -0
- package/dist/templates/github-actions/publish-workflow.d.ts.map +1 -0
- package/dist/templates/github-actions/publish-workflow.js +105 -0
- package/dist/templates/github-actions/publish-workflow.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key Vault access pre-flight check for KeyVault-backed NamedValues.
|
|
3
|
+
*
|
|
4
|
+
* Validates that the APIM service's managed identity has been granted access
|
|
5
|
+
* to the Key Vault secret. Uses Azure ARM APIs to:
|
|
6
|
+
* 1. Retrieve the APIM service identity (system- or user-assigned)
|
|
7
|
+
* 2. Locate the Key Vault resource in the subscription
|
|
8
|
+
* 3. Check RBAC role assignments or access policies
|
|
9
|
+
*
|
|
10
|
+
* This check is best-effort when infrastructure queries fail (ARM token,
|
|
11
|
+
* vault in another subscription, etc.) — a warning is logged and the check
|
|
12
|
+
* is skipped. Hard errors are raised only for definitive misconfigurations
|
|
13
|
+
* such as "APIM has no managed identity" or "no matching RBAC / access policy".
|
|
14
|
+
*/
|
|
15
|
+
import { DefaultAzureCredential } from '@azure/identity';
|
|
16
|
+
import { logger } from '../lib/logger.js';
|
|
17
|
+
import { USER_AGENT } from '../lib/user-agent.js';
|
|
18
|
+
/* ------------------------------------------------------------------ */
|
|
19
|
+
/* ARM API versions */
|
|
20
|
+
/* ------------------------------------------------------------------ */
|
|
21
|
+
const APIM_API_VERSION = '2024-05-01';
|
|
22
|
+
const RESOURCES_API_VERSION = '2021-04-01';
|
|
23
|
+
const KEYVAULT_API_VERSION = '2023-07-01';
|
|
24
|
+
const AUTHZ_API_VERSION = '2022-04-01';
|
|
25
|
+
/** ARM management scope */
|
|
26
|
+
const ARM_SCOPE = 'https://management.azure.com/.default';
|
|
27
|
+
/**
|
|
28
|
+
* Well-known Azure RBAC role-definition GUIDs that grant
|
|
29
|
+
* the `Microsoft.KeyVault/vaults/secrets/getSecret/action` data-action.
|
|
30
|
+
* Used only as a fast-path optimisation — if none match we still resolve
|
|
31
|
+
* each role definition to inspect its dataActions.
|
|
32
|
+
*/
|
|
33
|
+
const SECRET_GET_ROLE_IDS = new Set([
|
|
34
|
+
'4633458b-17de-408a-b874-0445c86b69e6', // Key Vault Secrets User
|
|
35
|
+
'b86a8fe4-44ce-4948-aee5-eccb2c155cd7', // Key Vault Secrets Officer
|
|
36
|
+
'00482a5a-887f-4fb3-b363-3b7fe8e74483', // Key Vault Administrator
|
|
37
|
+
]);
|
|
38
|
+
/* ------------------------------------------------------------------ */
|
|
39
|
+
/* Public types */
|
|
40
|
+
/* ------------------------------------------------------------------ */
|
|
41
|
+
/**
|
|
42
|
+
* Error thrown when the APIM managed identity clearly lacks access to
|
|
43
|
+
* Key Vault, or the APIM service is missing the required identity
|
|
44
|
+
* configuration. Signals that the publish should fail immediately.
|
|
45
|
+
*/
|
|
46
|
+
export class KeyVaultAccessError extends Error {
|
|
47
|
+
constructor(message, options) {
|
|
48
|
+
super(message, options);
|
|
49
|
+
this.name = 'KeyVaultAccessError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/* ------------------------------------------------------------------ */
|
|
53
|
+
/* Default production implementations */
|
|
54
|
+
/* ------------------------------------------------------------------ */
|
|
55
|
+
function defaultTokenProviderFactory() {
|
|
56
|
+
return new DefaultAzureCredential();
|
|
57
|
+
}
|
|
58
|
+
async function defaultArmRequest(url, token) {
|
|
59
|
+
const headers = new Headers();
|
|
60
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
61
|
+
headers.set('Accept', 'application/json');
|
|
62
|
+
headers.set('User-Agent', USER_AGENT);
|
|
63
|
+
const response = await fetch(url, { headers });
|
|
64
|
+
return {
|
|
65
|
+
status: response.status,
|
|
66
|
+
json: () => response.json(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/* ------------------------------------------------------------------ */
|
|
70
|
+
/* Main entry point */
|
|
71
|
+
/* ------------------------------------------------------------------ */
|
|
72
|
+
/**
|
|
73
|
+
* Verify that the APIM service's managed identity has access to the
|
|
74
|
+
* specified Key Vault secret via ARM RBAC or access policies.
|
|
75
|
+
*
|
|
76
|
+
* @param secretIdentifier Full Key Vault secret URI, e.g.
|
|
77
|
+
* `https://myvault.vault.azure.net/secrets/my-secret[/version]`
|
|
78
|
+
* @param identityClientId Client ID of the user-assigned managed identity
|
|
79
|
+
* that APIM will use. Omit for system-assigned identity.
|
|
80
|
+
* @param apimContext Subscription / resource-group / service-name of the
|
|
81
|
+
* APIM instance.
|
|
82
|
+
* @param tokenProviderFactory (testing) Override the ARM credential.
|
|
83
|
+
* @param armRequest (testing) Override the HTTP call.
|
|
84
|
+
*/
|
|
85
|
+
export async function checkKeyVaultSecretAccess(secretIdentifier, identityClientId, apimContext, tokenProviderFactory = defaultTokenProviderFactory, armRequest = defaultArmRequest) {
|
|
86
|
+
/* ---- 1. Parse vault name & secret name from the URI ---- */
|
|
87
|
+
let vaultName;
|
|
88
|
+
let secretName;
|
|
89
|
+
try {
|
|
90
|
+
const url = new URL(secretIdentifier);
|
|
91
|
+
vaultName = url.hostname.split('.')[0];
|
|
92
|
+
const segments = url.pathname.split('/').filter(Boolean);
|
|
93
|
+
secretName = segments[1] ?? '';
|
|
94
|
+
if (!secretName)
|
|
95
|
+
throw new Error('Missing secret name in path');
|
|
96
|
+
if (!vaultName)
|
|
97
|
+
throw new Error('Missing vault name in host');
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new KeyVaultAccessError(`Invalid Key Vault secretIdentifier: '${secretIdentifier}'`, { cause: error });
|
|
101
|
+
}
|
|
102
|
+
/* ---- 2. Acquire ARM token ---- */
|
|
103
|
+
const credential = tokenProviderFactory();
|
|
104
|
+
let token;
|
|
105
|
+
try {
|
|
106
|
+
token = (await credential.getToken(ARM_SCOPE)).token;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logger.warn(`Unable to acquire ARM token — skipping Key Vault pre-flight check. ` +
|
|
110
|
+
`(Error: ${error.message})`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
/* ---- 3. Get APIM service identity ---- */
|
|
114
|
+
const { principalId, identityLabel } = await resolveApimPrincipal(apimContext, identityClientId, token, armRequest);
|
|
115
|
+
if (!principalId)
|
|
116
|
+
return; // soft-skip — warning already logged
|
|
117
|
+
logger.debug(`Pre-flight: APIM ${identityLabel} principalId = ${principalId}`);
|
|
118
|
+
/* ---- 4. Locate the vault in the same subscription ---- */
|
|
119
|
+
const vault = await findVaultInSubscription(apimContext.subscriptionId, vaultName, token, armRequest);
|
|
120
|
+
if (!vault)
|
|
121
|
+
return; // soft-skip — warning already logged
|
|
122
|
+
/* ---- 5. Check RBAC or access-policy permissions ---- */
|
|
123
|
+
const vaultProps = vault.json
|
|
124
|
+
.properties;
|
|
125
|
+
if (vaultProps?.enableRbacAuthorization === true) {
|
|
126
|
+
await checkRbacAccess(vault.resourceId, principalId, identityLabel, vaultName, secretName, token, armRequest);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
checkAccessPolicies(vaultProps, principalId, identityLabel, vaultName, secretName);
|
|
130
|
+
}
|
|
131
|
+
logger.debug(`Key Vault access confirmed for APIM ${identityLabel} ` +
|
|
132
|
+
`on secret '${secretName}' in vault '${vaultName}'`);
|
|
133
|
+
}
|
|
134
|
+
/* ------------------------------------------------------------------ */
|
|
135
|
+
/* Internal helpers */
|
|
136
|
+
/* ------------------------------------------------------------------ */
|
|
137
|
+
/**
|
|
138
|
+
* Resolve the APIM managed-identity principal ID via the ARM service resource.
|
|
139
|
+
*
|
|
140
|
+
* Returns `{ principalId, identityLabel }` on success.
|
|
141
|
+
* Throws `KeyVaultAccessError` when APIM has no identity or the specified
|
|
142
|
+
* user-assigned identity is not found.
|
|
143
|
+
* Returns `{ principalId: undefined }` when the ARM call itself fails
|
|
144
|
+
* (warns and degrades gracefully).
|
|
145
|
+
*/
|
|
146
|
+
async function resolveApimPrincipal(ctx, identityClientId, token, armRequest) {
|
|
147
|
+
const identityLabel = identityClientId
|
|
148
|
+
? `user-assigned identity '${identityClientId}'`
|
|
149
|
+
: 'system-assigned identity';
|
|
150
|
+
const apimUrl = `https://management.azure.com/subscriptions/${ctx.subscriptionId}` +
|
|
151
|
+
`/resourceGroups/${ctx.resourceGroup}` +
|
|
152
|
+
`/providers/Microsoft.ApiManagement/service/${ctx.serviceName}` +
|
|
153
|
+
`?api-version=${APIM_API_VERSION}`;
|
|
154
|
+
let apimJson;
|
|
155
|
+
try {
|
|
156
|
+
const resp = await armRequest(apimUrl, token);
|
|
157
|
+
if (resp.status !== 200) {
|
|
158
|
+
logger.warn(`Failed to fetch APIM service (HTTP ${resp.status}) — ` +
|
|
159
|
+
`skipping Key Vault pre-flight check.`);
|
|
160
|
+
return { principalId: undefined, identityLabel };
|
|
161
|
+
}
|
|
162
|
+
apimJson = (await resp.json());
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
logger.warn(`Failed to fetch APIM service — skipping Key Vault pre-flight check. ` +
|
|
166
|
+
`(Error: ${error.message})`);
|
|
167
|
+
return { principalId: undefined, identityLabel };
|
|
168
|
+
}
|
|
169
|
+
const identity = apimJson.identity;
|
|
170
|
+
if (!identity) {
|
|
171
|
+
throw new KeyVaultAccessError(`APIM service '${ctx.serviceName}' has no managed identity configured. ` +
|
|
172
|
+
`KeyVault-backed NamedValues require a managed identity to access secrets.`);
|
|
173
|
+
}
|
|
174
|
+
let principalId;
|
|
175
|
+
if (identityClientId) {
|
|
176
|
+
// User-assigned: find the matching clientId in userAssignedIdentities
|
|
177
|
+
const uaMap = identity.userAssignedIdentities;
|
|
178
|
+
if (uaMap) {
|
|
179
|
+
for (const entry of Object.values(uaMap)) {
|
|
180
|
+
if (entry.clientId === identityClientId) {
|
|
181
|
+
principalId = entry.principalId;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!principalId) {
|
|
187
|
+
throw new KeyVaultAccessError(`User-assigned managed identity with clientId '${identityClientId}' ` +
|
|
188
|
+
`not found on APIM service '${ctx.serviceName}'.`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
principalId = identity.principalId;
|
|
193
|
+
if (!principalId) {
|
|
194
|
+
throw new KeyVaultAccessError(`APIM service '${ctx.serviceName}' has no system-assigned managed ` +
|
|
195
|
+
`identity enabled. KeyVault-backed NamedValues require a managed identity.`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { principalId, identityLabel };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Search for a Key Vault resource in the given subscription by vault name.
|
|
202
|
+
* Returns the vault's resource ID and its full JSON, or `undefined` when the
|
|
203
|
+
* vault is not found (e.g. it lives in a different subscription).
|
|
204
|
+
*/
|
|
205
|
+
async function findVaultInSubscription(subscriptionId, vaultName, token, armRequest) {
|
|
206
|
+
const filter = encodeURIComponent(`resourceType eq 'Microsoft.KeyVault/vaults' and name eq '${vaultName}'`);
|
|
207
|
+
const listUrl = `https://management.azure.com/subscriptions/${subscriptionId}` +
|
|
208
|
+
`/resources?$filter=${filter}&api-version=${RESOURCES_API_VERSION}`;
|
|
209
|
+
let vaultResourceId;
|
|
210
|
+
try {
|
|
211
|
+
const resp = await armRequest(listUrl, token);
|
|
212
|
+
if (resp.status !== 200) {
|
|
213
|
+
logger.warn(`Failed to search for Key Vault '${vaultName}' (HTTP ${resp.status}) — ` +
|
|
214
|
+
`skipping pre-flight check.`);
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
const body = (await resp.json());
|
|
218
|
+
vaultResourceId = body.value?.[0]?.id;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
logger.warn(`Failed to search for Key Vault '${vaultName}' — skipping pre-flight check. ` +
|
|
222
|
+
`(Error: ${error.message})`);
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
if (!vaultResourceId) {
|
|
226
|
+
logger.warn(`Key Vault '${vaultName}' not found in subscription '${subscriptionId}'. ` +
|
|
227
|
+
`It may be in a different subscription — skipping pre-flight access check.`);
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
// GET the vault resource to read properties (RBAC mode, access policies)
|
|
231
|
+
const vaultUrl = `https://management.azure.com${vaultResourceId}` +
|
|
232
|
+
`?api-version=${KEYVAULT_API_VERSION}`;
|
|
233
|
+
try {
|
|
234
|
+
const resp = await armRequest(vaultUrl, token);
|
|
235
|
+
if (resp.status !== 200) {
|
|
236
|
+
logger.warn(`Failed to read Key Vault '${vaultName}' (HTTP ${resp.status}) — ` +
|
|
237
|
+
`skipping pre-flight check.`);
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
const json = await resp.json();
|
|
241
|
+
return { resourceId: vaultResourceId, json };
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
logger.warn(`Failed to read Key Vault '${vaultName}' — skipping pre-flight check. ` +
|
|
245
|
+
`(Error: ${error.message})`);
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** The data-action that grants secret read access in Key Vault RBAC. */
|
|
250
|
+
const SECRET_GET_DATA_ACTION = 'microsoft.keyvault/vaults/secrets/getsecret/action';
|
|
251
|
+
/** Wildcard patterns that also grant the secret-get data-action. */
|
|
252
|
+
const SECRET_GET_WILDCARDS = [
|
|
253
|
+
'microsoft.keyvault/vaults/secrets/*',
|
|
254
|
+
'microsoft.keyvault/vaults/*',
|
|
255
|
+
'microsoft.keyvault/*',
|
|
256
|
+
'*',
|
|
257
|
+
];
|
|
258
|
+
/**
|
|
259
|
+
* RBAC-mode check: list role assignments on the vault for the given principal.
|
|
260
|
+
* Throws `KeyVaultAccessError` when no assignments exist at all, or when
|
|
261
|
+
* none of the assigned roles grant the secret-get data-action.
|
|
262
|
+
*
|
|
263
|
+
* Strategy (fast-path first):
|
|
264
|
+
* 1. Check if any assignment matches a well-known role GUID — skip role
|
|
265
|
+
* definition resolution if so.
|
|
266
|
+
* 2. Otherwise, GET each role definition and inspect its `permissions[].dataActions`
|
|
267
|
+
* for the `Microsoft.KeyVault/vaults/secrets/getSecret/action` data-action.
|
|
268
|
+
*/
|
|
269
|
+
async function checkRbacAccess(vaultResourceId, principalId, identityLabel, vaultName, secretName, token, armRequest) {
|
|
270
|
+
const filter = encodeURIComponent(`principalId eq '${principalId}'`);
|
|
271
|
+
const url = `https://management.azure.com${vaultResourceId}` +
|
|
272
|
+
`/providers/Microsoft.Authorization/roleAssignments` +
|
|
273
|
+
`?$filter=${filter}&api-version=${AUTHZ_API_VERSION}`;
|
|
274
|
+
let assignments;
|
|
275
|
+
try {
|
|
276
|
+
const resp = await armRequest(url, token);
|
|
277
|
+
if (resp.status !== 200) {
|
|
278
|
+
logger.warn(`Failed to list role assignments (HTTP ${resp.status}) — ` +
|
|
279
|
+
`skipping RBAC pre-flight check for '${vaultName}'.`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const body = (await resp.json());
|
|
283
|
+
assignments = body.value ?? [];
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
logger.warn(`Failed to list role assignments — skipping RBAC pre-flight check. ` +
|
|
287
|
+
`(Error: ${error.message})`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (assignments.length === 0) {
|
|
291
|
+
throw new KeyVaultAccessError(`APIM ${identityLabel} (principalId: ${principalId}) has no RBAC role ` +
|
|
292
|
+
`assignments on Key Vault '${vaultName}'. ` +
|
|
293
|
+
`Grant the identity 'Key Vault Secrets User' role on the vault ` +
|
|
294
|
+
`so APIM can read secret '${secretName}'.`);
|
|
295
|
+
}
|
|
296
|
+
// --- Fast path: match well-known role GUIDs ---
|
|
297
|
+
const hasKnownRole = assignments.some((a) => {
|
|
298
|
+
const roleDefId = a.properties?.roleDefinitionId ?? '';
|
|
299
|
+
const roleGuid = roleDefId.split('/').pop()?.toLowerCase() ?? '';
|
|
300
|
+
return SECRET_GET_ROLE_IDS.has(roleGuid);
|
|
301
|
+
});
|
|
302
|
+
if (hasKnownRole) {
|
|
303
|
+
return; // confirmed — no need to resolve definitions
|
|
304
|
+
}
|
|
305
|
+
// --- Slow path: resolve each role definition and check dataActions ---
|
|
306
|
+
logger.debug(`No well-known secret role found; resolving ${assignments.length} role ` +
|
|
307
|
+
`definition(s) to check dataActions.`);
|
|
308
|
+
for (const assignment of assignments) {
|
|
309
|
+
const roleDefId = assignment.properties?.roleDefinitionId ?? '';
|
|
310
|
+
if (!roleDefId)
|
|
311
|
+
continue;
|
|
312
|
+
const hasAccess = await roleDefinitionGrantsSecretGet(roleDefId, token, armRequest);
|
|
313
|
+
if (hasAccess)
|
|
314
|
+
return; // confirmed via dataActions
|
|
315
|
+
}
|
|
316
|
+
throw new KeyVaultAccessError(`APIM ${identityLabel} (principalId: ${principalId}) has ` +
|
|
317
|
+
`${assignments.length} RBAC role assignment(s) on Key Vault '${vaultName}', ` +
|
|
318
|
+
`but none grant the '${SECRET_GET_DATA_ACTION}' data-action. ` +
|
|
319
|
+
`Grant the identity 'Key Vault Secrets User' role on the vault ` +
|
|
320
|
+
`so APIM can read secret '${secretName}'.`);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* GET a role definition by its full ARM ID and check whether any of its
|
|
324
|
+
* `permissions[].dataActions` grant the secret-get data-action.
|
|
325
|
+
*
|
|
326
|
+
* Returns `undefined` (rather than `false`) when the definition cannot be
|
|
327
|
+
* fetched — the caller should treat this as inconclusive and keep checking.
|
|
328
|
+
*/
|
|
329
|
+
async function roleDefinitionGrantsSecretGet(roleDefinitionId, token, armRequest) {
|
|
330
|
+
const url = `https://management.azure.com${roleDefinitionId}` +
|
|
331
|
+
`?api-version=${AUTHZ_API_VERSION}`;
|
|
332
|
+
let json;
|
|
333
|
+
try {
|
|
334
|
+
const resp = await armRequest(url, token);
|
|
335
|
+
if (resp.status !== 200) {
|
|
336
|
+
logger.debug(`Failed to fetch role definition (HTTP ${resp.status}): ${roleDefinitionId}`);
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
json = (await resp.json());
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
const props = json.properties;
|
|
345
|
+
const permissions = (props?.permissions ?? []);
|
|
346
|
+
for (const perm of permissions) {
|
|
347
|
+
const dataActions = (perm.dataActions ?? []);
|
|
348
|
+
const hasAction = dataActions.some((da) => {
|
|
349
|
+
const lower = da.toLowerCase();
|
|
350
|
+
return lower === SECRET_GET_DATA_ACTION ||
|
|
351
|
+
SECRET_GET_WILDCARDS.includes(lower);
|
|
352
|
+
});
|
|
353
|
+
if (hasAction) {
|
|
354
|
+
// Also check notDataActions don't deny it
|
|
355
|
+
const notDataActions = (perm.notDataActions ?? []);
|
|
356
|
+
const denied = notDataActions.some((nda) => {
|
|
357
|
+
const lower = nda.toLowerCase();
|
|
358
|
+
return lower === SECRET_GET_DATA_ACTION ||
|
|
359
|
+
SECRET_GET_WILDCARDS.includes(lower);
|
|
360
|
+
});
|
|
361
|
+
if (!denied)
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Access-policy mode check: scan the vault's `accessPolicies` array for the
|
|
369
|
+
* APIM principal with `secrets.get` permission.
|
|
370
|
+
* Throws `KeyVaultAccessError` when no matching policy is found.
|
|
371
|
+
*/
|
|
372
|
+
function checkAccessPolicies(vaultProperties, principalId, identityLabel, vaultName, secretName) {
|
|
373
|
+
const policies = vaultProperties?.accessPolicies ?? [];
|
|
374
|
+
const match = policies.find((p) => {
|
|
375
|
+
if (p.objectId?.toLowerCase() !== principalId.toLowerCase()) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
const perms = p.permissions;
|
|
379
|
+
const secretPerms = perms?.secrets ?? [];
|
|
380
|
+
return secretPerms.some((s) => s.toLowerCase() === 'get' || s.toLowerCase() === 'all');
|
|
381
|
+
});
|
|
382
|
+
if (!match) {
|
|
383
|
+
throw new KeyVaultAccessError(`APIM ${identityLabel} (principalId: ${principalId}) does not have an ` +
|
|
384
|
+
`access policy with 'get' secret permission on Key Vault '${vaultName}'. ` +
|
|
385
|
+
`Add an access policy granting 'get' on secrets for the identity, ` +
|
|
386
|
+
`or enable RBAC authorization and assign 'Key Vault Secrets User'. ` +
|
|
387
|
+
`Secret: '${secretName}'.`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
//# sourceMappingURL=keyvault-checker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyvault-checker.js","sourceRoot":"","sources":["../../src/services/keyvault-checker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T033: Override merger service
|
|
3
|
+
* Apply environment-specific overrides from OverrideConfig to resource JSON payloads.
|
|
4
|
+
* Deep-merges with case-insensitive key matching; warns on unknown override keys.
|
|
5
|
+
*/
|
|
6
|
+
import { ResourceDescriptor } from '../models/types.js';
|
|
7
|
+
import { OverrideConfig } from '../models/config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Apply environment overrides from OverrideConfig to a resource JSON payload.
|
|
10
|
+
* Deep-merges matching override properties using case-insensitive key matching.
|
|
11
|
+
* Logs a warning for any override keys that don't match resource type.
|
|
12
|
+
* Returns a new object (does not mutate input).
|
|
13
|
+
*
|
|
14
|
+
* @param descriptor - Resource descriptor (type + name identify the resource)
|
|
15
|
+
* @param json - Original resource JSON payload
|
|
16
|
+
* @param overrides - Environment-specific override configuration
|
|
17
|
+
* @returns New JSON object with overrides applied
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyOverrides(descriptor: ResourceDescriptor, json: Record<string, unknown>, overrides: OverrideConfig | undefined): Record<string, unknown>;
|
|
20
|
+
//# sourceMappingURL=override-merger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"override-merger.d.ts","sourceRoot":"","sources":["../../src/services/override-merger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIrD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,kBAAkB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,SAAS,EAAE,cAAc,GAAG,SAAS,GACpC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsCzB"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T033: Override merger service
|
|
3
|
+
* Apply environment-specific overrides from OverrideConfig to resource JSON payloads.
|
|
4
|
+
* Deep-merges with case-insensitive key matching; warns on unknown override keys.
|
|
5
|
+
*/
|
|
6
|
+
import { ResourceType } from '../models/resource-types.js';
|
|
7
|
+
import { logger } from '../lib/logger.js';
|
|
8
|
+
import { getNameFromNameParts } from '../lib/resource-path.js';
|
|
9
|
+
/**
|
|
10
|
+
* Apply environment overrides from OverrideConfig to a resource JSON payload.
|
|
11
|
+
* Deep-merges matching override properties using case-insensitive key matching.
|
|
12
|
+
* Logs a warning for any override keys that don't match resource type.
|
|
13
|
+
* Returns a new object (does not mutate input).
|
|
14
|
+
*
|
|
15
|
+
* @param descriptor - Resource descriptor (type + name identify the resource)
|
|
16
|
+
* @param json - Original resource JSON payload
|
|
17
|
+
* @param overrides - Environment-specific override configuration
|
|
18
|
+
* @returns New JSON object with overrides applied
|
|
19
|
+
*/
|
|
20
|
+
export function applyOverrides(descriptor, json, overrides) {
|
|
21
|
+
if (!overrides) {
|
|
22
|
+
return { ...json };
|
|
23
|
+
}
|
|
24
|
+
// Map resource type to override config section
|
|
25
|
+
const overrideSection = getOverrideSectionForType(descriptor.type, overrides);
|
|
26
|
+
if (!overrideSection) {
|
|
27
|
+
return { ...json };
|
|
28
|
+
}
|
|
29
|
+
// Find matching override using case-insensitive name comparison
|
|
30
|
+
const matchingKey = Object.keys(overrideSection).find((key) => key.toLowerCase() === getNameFromNameParts(descriptor.nameParts).toLowerCase());
|
|
31
|
+
if (!matchingKey) {
|
|
32
|
+
return { ...json };
|
|
33
|
+
}
|
|
34
|
+
const overrideValues = overrideSection[matchingKey];
|
|
35
|
+
if (overrideValues === null || overrideValues === undefined || typeof overrideValues !== 'object') {
|
|
36
|
+
return { ...json };
|
|
37
|
+
}
|
|
38
|
+
// ARM resources have all overridable fields inside 'properties'
|
|
39
|
+
// Wrap the override values inside 'properties' to merge at the correct level
|
|
40
|
+
const wrappedOverride = { properties: overrideValues };
|
|
41
|
+
// Deep-merge the override into the resource JSON
|
|
42
|
+
const result = deepMerge(json, wrappedOverride);
|
|
43
|
+
logger.debug(`Applied overrides to ${descriptor.type} '${descriptor.nameParts.join('/')}'`, { overrideKeys: Object.keys(overrideValues) });
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the override section for a given resource type.
|
|
48
|
+
* Returns the relevant Record<string, Override> map or undefined.
|
|
49
|
+
*/
|
|
50
|
+
function getOverrideSectionForType(type, overrides) {
|
|
51
|
+
switch (type) {
|
|
52
|
+
case ResourceType.NamedValue:
|
|
53
|
+
return overrides.namedValues;
|
|
54
|
+
case ResourceType.Backend:
|
|
55
|
+
return overrides.backends;
|
|
56
|
+
case ResourceType.Api:
|
|
57
|
+
return overrides.apis;
|
|
58
|
+
case ResourceType.Diagnostic:
|
|
59
|
+
return overrides.diagnostics;
|
|
60
|
+
case ResourceType.Logger:
|
|
61
|
+
return overrides.loggers;
|
|
62
|
+
default:
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Deep-merge two objects recursively.
|
|
68
|
+
* - Objects are merged recursively
|
|
69
|
+
* - Arrays are replaced (not merged)
|
|
70
|
+
* - Primitives from source override target
|
|
71
|
+
* - Returns a new object (immutable)
|
|
72
|
+
*
|
|
73
|
+
* @param target - Base object
|
|
74
|
+
* @param source - Override object
|
|
75
|
+
* @returns New merged object
|
|
76
|
+
*/
|
|
77
|
+
function deepMerge(target, source) {
|
|
78
|
+
const result = { ...target };
|
|
79
|
+
for (const [key, sourceValue] of Object.entries(source)) {
|
|
80
|
+
const targetValue = result[key];
|
|
81
|
+
// If both are plain objects, merge recursively
|
|
82
|
+
if (isPlainObject(sourceValue) &&
|
|
83
|
+
isPlainObject(targetValue)) {
|
|
84
|
+
result[key] = deepMerge(targetValue, sourceValue);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Otherwise replace (arrays, primitives, or type mismatch)
|
|
88
|
+
result[key] = sourceValue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if a value is a plain object (not an array, null, or other special object).
|
|
95
|
+
*/
|
|
96
|
+
function isPlainObject(value) {
|
|
97
|
+
return (typeof value === 'object' &&
|
|
98
|
+
value !== null &&
|
|
99
|
+
!Array.isArray(value) &&
|
|
100
|
+
Object.prototype.toString.call(value) === '[object Object]');
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=override-merger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"override-merger.js","sourceRoot":"","sources":["../../src/services/override-merger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T023: Product-specific extraction logic
|
|
3
|
+
* Product associations (apis.json, groups.json, tags.json), product policies, product wikis.
|
|
4
|
+
*/
|
|
5
|
+
import { IApimClient } from '../clients/iapim-client.js';
|
|
6
|
+
import { IArtifactStore } from '../clients/iartifact-store.js';
|
|
7
|
+
import { ApimServiceContext, ResourceDescriptor } from '../models/types.js';
|
|
8
|
+
import { FilterConfig } from '../models/config.js';
|
|
9
|
+
/**
|
|
10
|
+
* Result of product-specific extraction for a single product.
|
|
11
|
+
*/
|
|
12
|
+
export interface ProductExtractionResult {
|
|
13
|
+
productName: string;
|
|
14
|
+
apis: string[];
|
|
15
|
+
groups: string[];
|
|
16
|
+
policy: string | undefined;
|
|
17
|
+
wiki: boolean;
|
|
18
|
+
tags: string[];
|
|
19
|
+
policies: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Extract all product-specific resources for a single product.
|
|
23
|
+
* This includes API associations, group associations, policies, and wikis.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractProductResources(client: IApimClient, store: IArtifactStore, context: ApimServiceContext, productDescriptor: ResourceDescriptor, outputDir: string, _filter?: FilterConfig, _workspace?: string): Promise<ProductExtractionResult>;
|
|
26
|
+
//# sourceMappingURL=product-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-extractor.d.ts","sourceRoot":"","sources":["../../src/services/product-extractor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE5E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAKnD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,kBAAkB,EAC3B,iBAAiB,EAAE,kBAAkB,EACrC,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,YAAY,EACtB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,uBAAuB,CAAC,CAyClC"}
|