@peterhauge/apiops-cli 0.3.0-alpha.0 → 0.4.0-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.
Files changed (73) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/README.md +6 -0
  3. package/dist/cli/init-command.js +5 -5
  4. package/dist/cli/init-command.js.map +1 -1
  5. package/dist/lib/render-template.d.ts +10 -0
  6. package/dist/lib/render-template.d.ts.map +1 -0
  7. package/dist/lib/render-template.js +14 -0
  8. package/dist/lib/render-template.js.map +1 -0
  9. package/dist/services/api-extractor.d.ts.map +1 -1
  10. package/dist/services/api-extractor.js +10 -6
  11. package/dist/services/api-extractor.js.map +1 -1
  12. package/dist/services/extract-service.d.ts.map +1 -1
  13. package/dist/services/extract-service.js +4 -2
  14. package/dist/services/extract-service.js.map +1 -1
  15. package/dist/services/identity-guide-service.d.ts +3 -4
  16. package/dist/services/identity-guide-service.d.ts.map +1 -1
  17. package/dist/services/identity-guide-service.js +6 -40
  18. package/dist/services/identity-guide-service.js.map +1 -1
  19. package/dist/services/init-service.d.ts.map +1 -1
  20. package/dist/services/init-service.js +61 -28
  21. package/dist/services/init-service.js.map +1 -1
  22. package/dist/services/product-extractor.d.ts.map +1 -1
  23. package/dist/services/product-extractor.js +4 -2
  24. package/dist/services/product-extractor.js.map +1 -1
  25. package/dist/services/publish-service.d.ts.map +1 -1
  26. package/dist/services/publish-service.js +30 -0
  27. package/dist/services/publish-service.js.map +1 -1
  28. package/dist/services/resource-publisher.d.ts +5 -0
  29. package/dist/services/resource-publisher.d.ts.map +1 -1
  30. package/dist/services/resource-publisher.js +15 -1
  31. package/dist/services/resource-publisher.js.map +1 -1
  32. package/dist/services/secret-redaction-guard.d.ts +40 -0
  33. package/dist/services/secret-redaction-guard.d.ts.map +1 -0
  34. package/dist/services/secret-redaction-guard.js +92 -0
  35. package/dist/services/secret-redaction-guard.js.map +1 -0
  36. package/dist/services/secret-redactor.d.ts +18 -0
  37. package/dist/services/secret-redactor.d.ts.map +1 -1
  38. package/dist/services/secret-redactor.js +172 -0
  39. package/dist/services/secret-redactor.js.map +1 -1
  40. package/dist/templates/azure-devops/publish-pipeline.d.ts.map +1 -1
  41. package/dist/templates/azure-devops/publish-pipeline.js +62 -28
  42. package/dist/templates/azure-devops/publish-pipeline.js.map +1 -1
  43. package/dist/templates/configs/filter-config.d.ts.map +1 -1
  44. package/dist/templates/configs/filter-config.js +6 -118
  45. package/dist/templates/configs/filter-config.js.map +1 -1
  46. package/dist/templates/configs/override-config.d.ts.map +1 -1
  47. package/dist/templates/configs/override-config.js +7 -92
  48. package/dist/templates/configs/override-config.js.map +1 -1
  49. package/dist/templates/configs/schema-ref.d.ts +18 -0
  50. package/dist/templates/configs/schema-ref.d.ts.map +1 -0
  51. package/dist/templates/configs/schema-ref.js +26 -0
  52. package/dist/templates/configs/schema-ref.js.map +1 -0
  53. package/dist/templates/copilot/configure-filter-prompt.d.ts +10 -0
  54. package/dist/templates/copilot/configure-filter-prompt.d.ts.map +1 -0
  55. package/dist/templates/copilot/configure-filter-prompt.js +14 -0
  56. package/dist/templates/copilot/configure-filter-prompt.js.map +1 -0
  57. package/dist/templates/copilot/configure-overrides-prompt.d.ts +7 -0
  58. package/dist/templates/copilot/configure-overrides-prompt.d.ts.map +1 -0
  59. package/dist/templates/copilot/configure-overrides-prompt.js +12 -0
  60. package/dist/templates/copilot/configure-overrides-prompt.js.map +1 -0
  61. package/dist/templates/copilot/identity-setup-prompt.d.ts.map +1 -1
  62. package/dist/templates/copilot/identity-setup-prompt.js +19 -24
  63. package/dist/templates/copilot/identity-setup-prompt.js.map +1 -1
  64. package/dist/templates/generated/embedded-markdown.d.ts +8 -5
  65. package/dist/templates/generated/embedded-markdown.d.ts.map +1 -1
  66. package/dist/templates/generated/embedded-markdown.js +8 -5
  67. package/dist/templates/generated/embedded-markdown.js.map +1 -1
  68. package/dist/templates/github-actions/extract-workflow.js +4 -4
  69. package/dist/templates/github-actions/publish-workflow.d.ts +2 -1
  70. package/dist/templates/github-actions/publish-workflow.d.ts.map +1 -1
  71. package/dist/templates/github-actions/publish-workflow.js +103 -78
  72. package/dist/templates/github-actions/publish-workflow.js.map +1 -1
  73. package/package.json +8 -5
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Secret redaction pre-flight guard
3
+ *
4
+ * Scans the set of artifacts that are about to be published for leftover
5
+ * redaction markers ('*** REDACTED ***'). Any finding aborts the entire
6
+ * publish before a single PUT is issued (for both real and dry-run modes),
7
+ * so a partially-published service can never result from placeholder secrets.
8
+ *
9
+ * Detection mirrors the per-resource publish guards:
10
+ * - Policies: build the publish payload, apply overrides, then check the
11
+ * merged `properties.value` for the marker (an override may legitimately
12
+ * replace the value with clean content).
13
+ * - Named values: read the resource, apply overrides; KeyVault-backed values
14
+ * have their `value` stripped before publish and are therefore ignored;
15
+ * otherwise a `secret === true` value that exactly equals the marker is a
16
+ * finding.
17
+ */
18
+ import type { IArtifactStore } from '../clients/iartifact-store.js';
19
+ import type { PublishConfig } from '../models/config.js';
20
+ import type { ResourceDescriptor } from '../models/types.js';
21
+ /**
22
+ * A single artifact that still contains a redaction marker after overrides.
23
+ */
24
+ export interface RedactionMarkerFinding {
25
+ /** The descriptor for the offending artifact. */
26
+ descriptor: ResourceDescriptor;
27
+ /** Human-readable resource label (e.g. `apis/echo/policies/policy`). */
28
+ label: string;
29
+ /** Where the marker was found (e.g. `policy.xml`, `properties.value`). */
30
+ location: string;
31
+ }
32
+ /**
33
+ * Scan the supplied descriptors for leftover redaction markers in the content
34
+ * that would actually be published (i.e. after overrides are applied).
35
+ *
36
+ * Returns every finding so the caller can report all offenders at once rather
37
+ * than failing on the first one.
38
+ */
39
+ export declare function scanForRedactionMarkers(store: IArtifactStore, config: PublishConfig, descriptors: ResourceDescriptor[]): Promise<RedactionMarkerFinding[]>;
40
+ //# sourceMappingURL=secret-redaction-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-redaction-guard.d.ts","sourceRoot":"","sources":["../../src/services/secret-redaction-guard.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAO7D;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,iDAAiD;IACjD,UAAU,EAAE,kBAAkB,CAAC;IAC/B,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,kBAAkB,EAAE,GAChC,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAcnC"}
@@ -0,0 +1,92 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ /**
4
+ * Secret redaction pre-flight guard
5
+ *
6
+ * Scans the set of artifacts that are about to be published for leftover
7
+ * redaction markers ('*** REDACTED ***'). Any finding aborts the entire
8
+ * publish before a single PUT is issued (for both real and dry-run modes),
9
+ * so a partially-published service can never result from placeholder secrets.
10
+ *
11
+ * Detection mirrors the per-resource publish guards:
12
+ * - Policies: build the publish payload, apply overrides, then check the
13
+ * merged `properties.value` for the marker (an override may legitimately
14
+ * replace the value with clean content).
15
+ * - Named values: read the resource, apply overrides; KeyVault-backed values
16
+ * have their `value` stripped before publish and are therefore ignored;
17
+ * otherwise a `secret === true` value that exactly equals the marker is a
18
+ * finding.
19
+ */
20
+ import { ResourceType } from '../models/resource-types.js';
21
+ import { applyOverrides } from './override-merger.js';
22
+ import { POLICY_TYPES } from './resource-publisher.js';
23
+ import { REDACTION_MARKER } from './secret-redactor.js';
24
+ import { buildResourceLabel } from '../lib/resource-uri.js';
25
+ /**
26
+ * Scan the supplied descriptors for leftover redaction markers in the content
27
+ * that would actually be published (i.e. after overrides are applied).
28
+ *
29
+ * Returns every finding so the caller can report all offenders at once rather
30
+ * than failing on the first one.
31
+ */
32
+ export async function scanForRedactionMarkers(store, config, descriptors) {
33
+ const findings = [];
34
+ for (const descriptor of descriptors) {
35
+ if (POLICY_TYPES.has(descriptor.type)) {
36
+ const finding = await scanPolicy(store, config, descriptor);
37
+ if (finding)
38
+ findings.push(finding);
39
+ }
40
+ else if (descriptor.type === ResourceType.NamedValue) {
41
+ const finding = await scanNamedValue(store, config, descriptor);
42
+ if (finding)
43
+ findings.push(finding);
44
+ }
45
+ }
46
+ return findings;
47
+ }
48
+ async function scanPolicy(store, config, descriptor) {
49
+ const policyContent = await store.readContent(config.sourceDir, descriptor, 'policy');
50
+ if (!policyContent) {
51
+ return undefined;
52
+ }
53
+ const payload = {
54
+ properties: {
55
+ value: policyContent.content,
56
+ format: 'rawxml',
57
+ },
58
+ };
59
+ const merged = applyOverrides(descriptor, payload, config.overrides);
60
+ const mergedProps = merged.properties;
61
+ const mergedValue = mergedProps?.value;
62
+ if (typeof mergedValue === 'string' && mergedValue.includes(REDACTION_MARKER)) {
63
+ return {
64
+ descriptor,
65
+ label: buildResourceLabel(descriptor),
66
+ location: 'policy.xml',
67
+ };
68
+ }
69
+ return undefined;
70
+ }
71
+ async function scanNamedValue(store, config, descriptor) {
72
+ const json = await store.readResource(config.sourceDir, descriptor);
73
+ if (!json) {
74
+ return undefined;
75
+ }
76
+ const merged = applyOverrides(descriptor, json, config.overrides);
77
+ const props = merged.properties;
78
+ // KeyVault-backed named values have `properties.value` stripped before publish,
79
+ // so any marker still present in the file is never sent to APIM.
80
+ if (props?.keyVault != null) {
81
+ return undefined;
82
+ }
83
+ if (props?.secret === true && props.value === REDACTION_MARKER) {
84
+ return {
85
+ descriptor,
86
+ label: buildResourceLabel(descriptor),
87
+ location: 'properties.value',
88
+ };
89
+ }
90
+ return undefined;
91
+ }
92
+ //# sourceMappingURL=secret-redaction-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-redaction-guard.js","sourceRoot":"","sources":["../../src/services/secret-redaction-guard.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,9 @@
6
6
  import { ResourceDescriptor } from '../models/types.js';
7
7
  /** Marker used to replace secret values in extracted artifacts */
8
8
  export declare const REDACTION_MARKER = "*** REDACTED ***";
9
+ export interface PolicySecretFinding {
10
+ location: string;
11
+ }
9
12
  /**
10
13
  * Redact secret values from a resource's JSON payload.
11
14
  *
@@ -17,4 +20,19 @@ export declare const REDACTION_MARKER = "*** REDACTED ***";
17
20
  * @returns JSON with secrets redacted
18
21
  */
19
22
  export declare function redactSecrets(descriptor: ResourceDescriptor, json: Record<string, unknown>): Record<string, unknown>;
23
+ /**
24
+ * Redact inline literal secrets in policy XML content.
25
+ */
26
+ export declare function redactPolicySecrets(policyContent: string): {
27
+ redactedContent: string;
28
+ findings: PolicySecretFinding[];
29
+ };
30
+ /**
31
+ * Redact inline literal secrets in policy XML content and emit a warning log
32
+ * for every finding. This is the entry point services should use so that
33
+ * redaction and warning always happen together; the underlying
34
+ * {@link redactPolicySecrets} (pure, exported) and `warnPolicySecretRedactions`
35
+ * (private) helpers stay separate for testability.
36
+ */
37
+ export declare function redactAndWarnPolicySecrets(descriptor: ResourceDescriptor, policyContent: string): string;
20
38
  //# sourceMappingURL=secret-redactor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"secret-redactor.d.ts","sourceRoot":"","sources":["../../src/services/secret-redactor.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAIxD,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,kBAAkB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4BzB"}
1
+ {"version":3,"file":"secret-redactor.d.ts","sourceRoot":"","sources":["../../src/services/secret-redactor.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAKxD,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAoBnD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,kBAAkB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4BzB;AAqCD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,GACpB;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAgI9D;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,kBAAkB,EAC9B,aAAa,EAAE,MAAM,GACpB,MAAM,CAIR"}
@@ -8,8 +8,27 @@
8
8
  import { ResourceType } from '../models/resource-types.js';
9
9
  import { logger } from '../lib/logger.js';
10
10
  import { getNamePart } from '../lib/resource-path.js';
11
+ import { buildResourceLabel } from '../lib/resource-uri.js';
11
12
  /** Marker used to replace secret values in extracted artifacts */
12
13
  export const REDACTION_MARKER = '*** REDACTED ***';
14
+ const NAMED_VALUE_REFERENCE_PATTERN = /^\s*\{\{[^{}]+\}\}\s*$/;
15
+ // Headers where inline literal values are typically secrets and should be
16
+ // redacted when present in policy XML. Extend this allow-list when APIM adds
17
+ // new secret-bearing header conventions.
18
+ const SECRET_HEADER_NAMES = new Set([
19
+ 'authorization',
20
+ 'ocp-apim-subscription-key',
21
+ 'x-functions-key',
22
+ 'api-key',
23
+ ]);
24
+ // Query parameters commonly used to carry secrets/tokens in APIM policies.
25
+ // Keep focused on secret-bearing names to avoid over-redacting non-secrets.
26
+ const SECRET_QUERY_PARAMETER_NAMES = new Set([
27
+ 'code',
28
+ 'sig',
29
+ 'subscription-key',
30
+ ]);
31
+ const BEARER_TOKEN_PATTERN = /^(\s*Bearer)(\s+)(.*?)(\s*)$/i;
13
32
  /**
14
33
  * Redact secret values from a resource's JSON payload.
15
34
  *
@@ -44,4 +63,157 @@ export function redactSecrets(descriptor, json) {
44
63
  logger.debug(`Redacted secret value for named value "${getNamePart(descriptor.nameParts, 0)}"`);
45
64
  return redacted;
46
65
  }
66
+ function isApimNamedValueReference(value) {
67
+ return NAMED_VALUE_REFERENCE_PATTERN.test(value);
68
+ }
69
+ function shouldRedactLiteral(value) {
70
+ const trimmed = value.trim();
71
+ if (!trimmed || trimmed === REDACTION_MARKER) {
72
+ return false;
73
+ }
74
+ return !isApimNamedValueReference(trimmed);
75
+ }
76
+ function redactAuthorizationHeaderValue(value) {
77
+ const bearerMatch = BEARER_TOKEN_PATTERN.exec(value);
78
+ if (bearerMatch) {
79
+ const [, scheme, spacing, tokenValue, suffix] = bearerMatch;
80
+ if (!shouldRedactLiteral(tokenValue)) {
81
+ return { redactedValue: value, wasRedacted: false };
82
+ }
83
+ return {
84
+ redactedValue: `${scheme}${spacing}${REDACTION_MARKER}${suffix}`,
85
+ wasRedacted: true,
86
+ };
87
+ }
88
+ if (!shouldRedactLiteral(value)) {
89
+ return { redactedValue: value, wasRedacted: false };
90
+ }
91
+ return { redactedValue: REDACTION_MARKER, wasRedacted: true };
92
+ }
93
+ /**
94
+ * Redact inline literal secrets in policy XML content.
95
+ */
96
+ export function redactPolicySecrets(policyContent) {
97
+ const findings = [];
98
+ const addFinding = (location) => {
99
+ findings.push({ location });
100
+ };
101
+ let redacted = policyContent;
102
+ redacted = redacted.replace(/<set-header\b[\s\S]*?<\/set-header>/gi, (setHeaderBlock) => {
103
+ const nameMatch = /\bname\s*=\s*["']([^"']+)["']/i.exec(setHeaderBlock);
104
+ const headerName = nameMatch?.[1]?.toLowerCase();
105
+ if (!headerName || !SECRET_HEADER_NAMES.has(headerName)) {
106
+ return setHeaderBlock;
107
+ }
108
+ return setHeaderBlock.replace(/(<value\b[^>]*>)([\s\S]*?)(<\/value>)/gi, (_full, openTag, value, closeTag) => {
109
+ const shouldRedactHeaderValue = shouldRedactLiteral(value);
110
+ const { redactedValue, wasRedacted } = headerName === 'authorization'
111
+ ? redactAuthorizationHeaderValue(value)
112
+ : {
113
+ redactedValue: shouldRedactHeaderValue ? REDACTION_MARKER : value,
114
+ wasRedacted: shouldRedactHeaderValue,
115
+ };
116
+ if (!wasRedacted) {
117
+ return `${openTag}${value}${closeTag}`;
118
+ }
119
+ addFinding(`set-header[${headerName}]`);
120
+ return `${openTag}${redactedValue}${closeTag}`;
121
+ });
122
+ });
123
+ redacted = redacted.replace(/<set-query-parameter\b[\s\S]*?<\/set-query-parameter>/gi, (setQueryBlock) => {
124
+ const nameMatch = /\bname\s*=\s*["']([^"']+)["']/i.exec(setQueryBlock);
125
+ const parameterName = nameMatch?.[1]?.toLowerCase();
126
+ if (!parameterName || !SECRET_QUERY_PARAMETER_NAMES.has(parameterName)) {
127
+ return setQueryBlock;
128
+ }
129
+ return setQueryBlock.replace(/(<value\b[^>]*>)([\s\S]*?)(<\/value>)/gi, (_full, openTag, value, closeTag) => {
130
+ if (!shouldRedactLiteral(value)) {
131
+ return `${openTag}${value}${closeTag}`;
132
+ }
133
+ addFinding(`set-query-parameter[${parameterName}]`);
134
+ return `${openTag}${REDACTION_MARKER}${closeTag}`;
135
+ });
136
+ });
137
+ redacted = redacted.replace(/<authentication-basic\b[^>]*>/gi, (tag) => {
138
+ return tag.replace(/(\bpassword\s*=\s*["'])([^"']*)(["'])/i, (_full, prefix, value, suffix) => {
139
+ if (!shouldRedactLiteral(value)) {
140
+ return `${prefix}${value}${suffix}`;
141
+ }
142
+ addFinding('authentication-basic@password');
143
+ return `${prefix}${REDACTION_MARKER}${suffix}`;
144
+ });
145
+ });
146
+ redacted = redacted.replace(/<authentication-certificate\b[^>]*>/gi, (tag) => {
147
+ return tag.replace(/(\bbody\s*=\s*["'])([^"']*)(["'])/i, (_full, prefix, value, suffix) => {
148
+ if (!shouldRedactLiteral(value)) {
149
+ return `${prefix}${value}${suffix}`;
150
+ }
151
+ addFinding('authentication-certificate@body');
152
+ return `${prefix}${REDACTION_MARKER}${suffix}`;
153
+ });
154
+ });
155
+ redacted = redacted.replace(/<authentication-certificate\b[\s\S]*?<\/authentication-certificate>/gi, (certificateBlock) => {
156
+ return certificateBlock.replace(/(<certificate\b[^>]*>)([\s\S]*?)(<\/certificate>)/gi, (_full, openTag, value, closeTag) => {
157
+ if (!shouldRedactLiteral(value)) {
158
+ return `${openTag}${value}${closeTag}`;
159
+ }
160
+ addFinding('authentication-certificate/certificate');
161
+ return `${openTag}${REDACTION_MARKER}${closeTag}`;
162
+ });
163
+ });
164
+ for (const keySection of ['issuer-signing-keys', 'decryption-keys']) {
165
+ const sectionRegex = new RegExp(`<${keySection}\\b[\\s\\S]*?<\\/${keySection}>`, 'gi');
166
+ redacted = redacted.replace(sectionRegex, (sectionBlock) => {
167
+ return sectionBlock.replace(/(<key\b[^>]*>)([\s\S]*?)(<\/key>)/gi, (_full, openTag, value, closeTag) => {
168
+ if (!shouldRedactLiteral(value)) {
169
+ return `${openTag}${value}${closeTag}`;
170
+ }
171
+ addFinding(`validate-jwt ${keySection}/key`);
172
+ return `${openTag}${REDACTION_MARKER}${closeTag}`;
173
+ });
174
+ });
175
+ }
176
+ // AccountKey/SharedAccessKey fragments are used by storage/service-bus style
177
+ // connection strings. App Insights connection strings use InstrumentationKey
178
+ // and therefore do not match this pattern (allow-listed by design).
179
+ // Value exclusions:
180
+ // - ';' stops at the next connection-string key/value delimiter
181
+ // - whitespace/newlines avoid over-capturing adjacent text
182
+ // - '<' and '"' avoid crossing into XML tags/attributes
183
+ redacted = redacted.replace(/(AccountKey|SharedAccessKey)\s*=\s*([^;\r\n<"\s]+)/gi, (_full, key, value) => {
184
+ if (!shouldRedactLiteral(value)) {
185
+ return `${key}=${value}`;
186
+ }
187
+ addFinding(`connection-string[${key}]`);
188
+ return `${key}=${REDACTION_MARKER}`;
189
+ });
190
+ return {
191
+ redactedContent: redacted,
192
+ findings,
193
+ };
194
+ }
195
+ /**
196
+ * Redact inline literal secrets in policy XML content and emit a warning log
197
+ * for every finding. This is the entry point services should use so that
198
+ * redaction and warning always happen together; the underlying
199
+ * {@link redactPolicySecrets} (pure, exported) and `warnPolicySecretRedactions`
200
+ * (private) helpers stay separate for testability.
201
+ */
202
+ export function redactAndWarnPolicySecrets(descriptor, policyContent) {
203
+ const { redactedContent, findings } = redactPolicySecrets(policyContent);
204
+ warnPolicySecretRedactions(descriptor, findings);
205
+ return redactedContent;
206
+ }
207
+ /**
208
+ * Emit warning logs for policy secret redaction findings.
209
+ */
210
+ function warnPolicySecretRedactions(descriptor, findings) {
211
+ const label = buildResourceLabel(descriptor);
212
+ for (const finding of findings) {
213
+ logger.warn(`Found and redacted inline secret in ${label} (${finding.location}). ` +
214
+ `Publish will fail while '${REDACTION_MARKER}' remains. ` +
215
+ 'Update the policy to use a named value: ' +
216
+ 'https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-properties');
217
+ }
218
+ }
47
219
  //# sourceMappingURL=secret-redactor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"secret-redactor.js","sourceRoot":"","sources":["../../src/services/secret-redactor.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAClC;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAEnD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAC3B,UAA8B,EAC9B,IAA6B;IAE7B,IAAI,UAAU,CAAC,IAAI,KAAK,YAAY,CAAC,UAAU,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,UAAiD,CAAC;IAC1E,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6CAA6C;IAC7C,IAAI,UAAU,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,uEAAuE;IACvE,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,IAAI,UAAU,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtE,MAAM,CAAC,KAAK,CAAC,gBAAgB,WAAW,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC,4CAA4C,CAAC,CAAC;QAC/G,OAAO,IAAI,CAAC;IACd,CAAC;IAED,4CAA4C;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAA4B,CAAC;IAC7E,MAAM,aAAa,GAAG,QAAQ,CAAC,UAAqC,CAAC;IACrE,aAAa,CAAC,KAAK,GAAG,gBAAgB,CAAC;IAEvC,MAAM,CAAC,KAAK,CAAC,0CAA0C,WAAW,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;IAChG,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"secret-redactor.js","sourceRoot":"","sources":["../../src/services/secret-redactor.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"publish-pipeline.d.ts","sourceRoot":"","sources":["../../../src/templates/azure-devops/publish-pipeline.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CA4H7E"}
1
+ {"version":3,"file":"publish-pipeline.d.ts","sourceRoot":"","sources":["../../../src/templates/azure-devops/publish-pipeline.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CA6J7E"}
@@ -7,17 +7,18 @@
7
7
  export function generatePublishPipeline(config) {
8
8
  const defaultEnvironment = config.environments[0] ?? 'dev';
9
9
  const envValues = config.environments.map((env) => ` - '${env}'`).join('\n');
10
- const stages = config.environments.map((env) => {
11
- const envUpper = env.toUpperCase();
12
- return `- stage: Publish_${env}
13
- displayName: 'Publish to ${env}'
14
- condition: eq('\${{ parameters.ENVIRONMENT }}', '${env}')
10
+ // A single parameterized stage drives all environments. The ENVIRONMENT
11
+ // parameter is resolved at template (compile) time, so it can select the
12
+ // variable group, deployment environment, config file, and env-suffixed
13
+ // variable names (via upper()) without duplicating the stage per environment.
14
+ const stage = `- stage: Publish
15
+ displayName: 'Publish to \${{ parameters.ENVIRONMENT }}'
15
16
  variables:
16
- - group: apim-${env}
17
+ - group: apim-\${{ parameters.ENVIRONMENT }}
17
18
  jobs:
18
19
  - deployment: Deploy
19
- displayName: 'Deploy to ${env}'
20
- environment: ${env}
20
+ displayName: 'Deploy to \${{ parameters.ENVIRONMENT }}'
21
+ environment: \${{ parameters.ENVIRONMENT }}
21
22
  pool:
22
23
  vmImage: 'ubuntu-latest'
23
24
  strategy:
@@ -46,54 +47,87 @@ export function generatePublishPipeline(config) {
46
47
  displayName: 'Install dependencies'
47
48
 
48
49
  - task: replacetokens@6
49
- displayName: 'Substitute tokens in configuration.${env}.yaml'
50
+ displayName: 'Substitute tokens in configuration.\${{ parameters.ENVIRONMENT }}.yaml'
50
51
  inputs:
51
- sources: 'configuration.${env}.yaml'
52
+ sources: 'configuration.\${{ parameters.ENVIRONMENT }}.yaml'
52
53
  tokenPattern: 'custom'
53
54
  tokenPrefix: '{#['
54
55
  tokenSuffix: ']#}'
55
56
  missingVarAction: 'keep'
57
+ missingVarLog: 'error'
56
58
 
57
59
  - script: |
58
- if grep -q '{#\\[' configuration.${env}.yaml; then
59
- echo "Unresolved tokens remain in configuration.${env}.yaml"
60
- grep -o '{#\\[[^]]*\\]#}' configuration.${env}.yaml | sort -u
60
+ if grep -q '{#\\[' configuration.\${{ parameters.ENVIRONMENT }}.yaml; then
61
+ echo "Unresolved tokens remain in configuration.\${{ parameters.ENVIRONMENT }}.yaml"
62
+ grep -o '{#\\[[^]]*\\]#}' configuration.\${{ parameters.ENVIRONMENT }}.yaml | sort -u
61
63
  exit 1
62
64
  fi
63
- displayName: 'Validate token substitution (${env})'
65
+ displayName: 'Validate token substitution'
64
66
 
65
67
  - task: AzureCLI@2
66
- displayName: 'Publish to ${env} (incremental - last commit only)'
67
- condition: ne('\${{ parameters.COMMIT_ID_CHOICE }}', 'publish-all-artifacts-in-repo')
68
+ displayName: 'Dry-run validation (incremental)'
69
+ condition: and(succeeded(), ne('\${{ parameters.COMMIT_ID_CHOICE }}', 'publish-all-artifacts-in-repo'))
68
70
  inputs:
69
- azureSubscription: 'AZURE_SERVICE_CONNECTION_${envUpper}'
71
+ azureSubscription: 'AZURE_SERVICE_CONNECTION_\${{ upper(parameters.ENVIRONMENT) }}'
70
72
  scriptType: 'bash'
71
73
  scriptLocation: 'inlineScript'
72
74
  inlineScript: |
73
75
  npx apiops publish \\
74
- --resource-group $(APIM_RESOURCE_GROUP_${envUpper}) \\
75
- --service-name $(APIM_SERVICE_NAME_${envUpper}) \\
76
+ --resource-group $(APIM_RESOURCE_GROUP_\${{ upper(parameters.ENVIRONMENT) }}) \\
77
+ --service-name $(APIM_SERVICE_NAME_\${{ upper(parameters.ENVIRONMENT) }}) \\
76
78
  --source ${config.artifactDir} \\
77
- --overrides configuration.${env}.yaml \\
79
+ --overrides configuration.\${{ parameters.ENVIRONMENT }}.yaml \\
80
+ --commit-id $(Build.SourceVersion) \\
81
+ --subscription-id $(AZURE_SUBSCRIPTION_ID) \\
82
+ --dry-run
83
+
84
+ - task: AzureCLI@2
85
+ displayName: 'Dry-run validation (all artifacts)'
86
+ condition: and(succeeded(), eq('\${{ parameters.COMMIT_ID_CHOICE }}', 'publish-all-artifacts-in-repo'))
87
+ inputs:
88
+ azureSubscription: 'AZURE_SERVICE_CONNECTION_\${{ upper(parameters.ENVIRONMENT) }}'
89
+ scriptType: 'bash'
90
+ scriptLocation: 'inlineScript'
91
+ inlineScript: |
92
+ npx apiops publish \\
93
+ --resource-group $(APIM_RESOURCE_GROUP_\${{ upper(parameters.ENVIRONMENT) }}) \\
94
+ --service-name $(APIM_SERVICE_NAME_\${{ upper(parameters.ENVIRONMENT) }}) \\
95
+ --source ${config.artifactDir} \\
96
+ --overrides configuration.\${{ parameters.ENVIRONMENT }}.yaml \\
97
+ --subscription-id $(AZURE_SUBSCRIPTION_ID) \\
98
+ --dry-run
99
+
100
+ - task: AzureCLI@2
101
+ displayName: 'Publish (incremental - last commit only)'
102
+ condition: and(succeeded(), ne('\${{ parameters.COMMIT_ID_CHOICE }}', 'publish-all-artifacts-in-repo'))
103
+ inputs:
104
+ azureSubscription: 'AZURE_SERVICE_CONNECTION_\${{ upper(parameters.ENVIRONMENT) }}'
105
+ scriptType: 'bash'
106
+ scriptLocation: 'inlineScript'
107
+ inlineScript: |
108
+ npx apiops publish \\
109
+ --resource-group $(APIM_RESOURCE_GROUP_\${{ upper(parameters.ENVIRONMENT) }}) \\
110
+ --service-name $(APIM_SERVICE_NAME_\${{ upper(parameters.ENVIRONMENT) }}) \\
111
+ --source ${config.artifactDir} \\
112
+ --overrides configuration.\${{ parameters.ENVIRONMENT }}.yaml \\
78
113
  --commit-id $(Build.SourceVersion) \\
79
114
  --subscription-id $(AZURE_SUBSCRIPTION_ID)
80
115
 
81
116
  - task: AzureCLI@2
82
- displayName: 'Publish to ${env} (all artifacts)'
83
- condition: eq('\${{ parameters.COMMIT_ID_CHOICE }}', 'publish-all-artifacts-in-repo')
117
+ displayName: 'Publish (all artifacts)'
118
+ condition: and(succeeded(), eq('\${{ parameters.COMMIT_ID_CHOICE }}', 'publish-all-artifacts-in-repo'))
84
119
  inputs:
85
- azureSubscription: 'AZURE_SERVICE_CONNECTION_${envUpper}'
120
+ azureSubscription: 'AZURE_SERVICE_CONNECTION_\${{ upper(parameters.ENVIRONMENT) }}'
86
121
  scriptType: 'bash'
87
122
  scriptLocation: 'inlineScript'
88
123
  inlineScript: |
89
124
  npx apiops publish \\
90
- --resource-group $(APIM_RESOURCE_GROUP_${envUpper}) \\
91
- --service-name $(APIM_SERVICE_NAME_${envUpper}) \\
125
+ --resource-group $(APIM_RESOURCE_GROUP_\${{ upper(parameters.ENVIRONMENT) }}) \\
126
+ --service-name $(APIM_SERVICE_NAME_\${{ upper(parameters.ENVIRONMENT) }}) \\
92
127
  --source ${config.artifactDir} \\
93
- --overrides configuration.${env}.yaml \\
128
+ --overrides configuration.\${{ parameters.ENVIRONMENT }}.yaml \\
94
129
  --subscription-id $(AZURE_SUBSCRIPTION_ID)
95
130
  `;
96
- }).join('\n');
97
131
  return `# Azure DevOps Pipeline: Run APIM Publisher
98
132
 
99
133
  trigger:
@@ -123,7 +157,7 @@ parameters:
123
157
  ${envValues}
124
158
 
125
159
  stages:
126
- ${stages}
160
+ ${stage}
127
161
  `;
128
162
  }
129
163
  //# sourceMappingURL=publish-pipeline.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"publish-pipeline.js","sourceRoot":"","sources":["../../../src/templates/azure-devops/publish-pipeline.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAClC;;;GAGG;AAOH,MAAM,UAAU,uBAAuB,CAAC,MAA6B;IACnE,MAAM,kBAAkB,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;IAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAElF,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7C,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAEnC,OAAO,oBAAoB,GAAG;6BACL,GAAG;qDACqB,GAAG;;oBAEpC,GAAG;;;gCAGS,GAAG;qBACd,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mEA6B2C,GAAG;;4CAE1B,GAAG;;;;;;;qDAOM,GAAG;sEACc,GAAG;8DACX,GAAG;;;6DAGJ,GAAG;;;2CAGrB,GAAG;;;iEAGmB,QAAQ;;;;;+DAKV,QAAQ;2DACZ,QAAQ;iCAClC,MAAM,CAAC,WAAW;kDACD,GAAG;;;;;2CAKV,GAAG;;;iEAGmB,QAAQ;;;;;+DAKV,QAAQ;2DACZ,QAAQ;iCAClC,MAAM,CAAC,WAAW;kDACD,GAAG;;CAEpD,CAAC;IACA,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;;;;;;;;WAQE,MAAM,CAAC,WAAW;;;;;;;;;;;;;;;;gBAgBb,kBAAkB;;EAEhC,SAAS;;;EAGT,MAAM;CACP,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"publish-pipeline.js","sourceRoot":"","sources":["../../../src/templates/azure-devops/publish-pipeline.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAClC;;;GAGG;AAOH,MAAM,UAAU,uBAAuB,CAAC,MAA6B;IACnE,MAAM,kBAAkB,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;IAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAElF,wEAAwE;IACxE,yEAAyE;IACzE,wEAAwE;IACxE,8EAA8E;IAC9E,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCAgEiB,MAAM,CAAC,WAAW;;;;;;;;;;;;;;;;;iCAiBlB,MAAM,CAAC,WAAW;;;;;;;;;;;;;;;;iCAgBlB,MAAM,CAAC,WAAW;;;;;;;;;;;;;;;;iCAgBlB,MAAM,CAAC,WAAW;;;CAGlD,CAAC;IAEA,OAAO;;;;;;;;WAQE,MAAM,CAAC,WAAW;;;;;;;;;;;;;;;;gBAgBb,kBAAkB;;EAEhC,SAAS;;;EAGT,KAAK;CACN,CAAC;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"filter-config.d.ts","sourceRoot":"","sources":["../../../src/templates/configs/filter-config.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,wBAAgB,oBAAoB,IAAI,MAAM,CAuH7C"}
1
+ {"version":3,"file":"filter-config.d.ts","sourceRoot":"","sources":["../../../src/templates/configs/filter-config.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAMH,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C"}