@beesolve/aws-accounts 1.2.0 → 1.3.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/dist/applyLogic.js +436 -297
- package/dist/awsConfig.js +219 -187
- package/dist/cli.js +49 -23
- package/dist/commands/remote.js +69 -3
- package/dist/commands/validate.js +34 -4
- package/dist/diff.js +191 -36
- package/dist/operations.js +36 -2
- package/dist/scanLogic.js +91 -12
- package/dist/state.js +85 -5
- package/dist-lambda/handler.mjs +644 -316
- package/dist-lambda/lambda.zip +0 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { basename } from "node:path";
|
|
3
4
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
4
5
|
import { IAMClient } from "@aws-sdk/client-iam";
|
|
5
6
|
import { LambdaClient } from "@aws-sdk/client-lambda";
|
|
@@ -24,13 +25,15 @@ import {
|
|
|
24
25
|
runRemoteInit,
|
|
25
26
|
runRemotePlan,
|
|
26
27
|
runRemoteApply,
|
|
27
|
-
runRemoteUpgrade
|
|
28
|
+
runRemoteUpgrade,
|
|
29
|
+
runRemoteDrift
|
|
28
30
|
} from "./commands/remote.js";
|
|
29
31
|
import {
|
|
30
32
|
classifyCliError,
|
|
31
33
|
exitCodeForCliErrorKind,
|
|
32
34
|
toUsageError
|
|
33
35
|
} from "./error.js";
|
|
36
|
+
import { assertUnreachable } from "./helpers.js";
|
|
34
37
|
import { readAwsContextFromFile, readPackageVersion } from "./awsConfig.js";
|
|
35
38
|
const commands = [
|
|
36
39
|
"bootstrap",
|
|
@@ -42,7 +45,8 @@ const commands = [
|
|
|
42
45
|
"profile",
|
|
43
46
|
"plan",
|
|
44
47
|
"apply",
|
|
45
|
-
"upgrade"
|
|
48
|
+
"upgrade",
|
|
49
|
+
"drift"
|
|
46
50
|
];
|
|
47
51
|
function isCommandName(value) {
|
|
48
52
|
return commands.includes(value);
|
|
@@ -171,51 +175,73 @@ async function main() {
|
|
|
171
175
|
};
|
|
172
176
|
if (command === "bootstrap") {
|
|
173
177
|
await runRemoteBootstrap(remoteInput);
|
|
174
|
-
|
|
178
|
+
await printVersionBannerIfNeeded(logger);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (command === "scan") {
|
|
175
182
|
await runRemoteScan(remoteInput);
|
|
176
|
-
|
|
183
|
+
await printVersionBannerIfNeeded(logger);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (command === "init") {
|
|
177
187
|
await runRemoteInit(remoteInput);
|
|
178
|
-
|
|
188
|
+
await printVersionBannerIfNeeded(logger);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (command === "plan") {
|
|
179
192
|
await runRemotePlan(remoteInput);
|
|
180
|
-
|
|
193
|
+
await printVersionBannerIfNeeded(logger);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (command === "apply") {
|
|
181
197
|
await runRemoteApply(remoteInput);
|
|
182
|
-
|
|
198
|
+
await printVersionBannerIfNeeded(logger);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (command === "upgrade") {
|
|
183
202
|
await runRemoteUpgrade(remoteInput);
|
|
184
|
-
|
|
185
|
-
printHelp(logger);
|
|
186
|
-
process.exitCode = 1;
|
|
203
|
+
await printVersionBannerIfNeeded(logger);
|
|
187
204
|
return;
|
|
188
205
|
}
|
|
189
|
-
|
|
206
|
+
if (command === "drift") {
|
|
207
|
+
await runRemoteDrift(remoteInput);
|
|
208
|
+
await printVersionBannerIfNeeded(logger);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
assertUnreachable(command, `Unhandled remote command: "${command}"`);
|
|
190
212
|
}
|
|
191
213
|
function printHelp(logger) {
|
|
214
|
+
const cmd = basename(process.argv[1], ".js");
|
|
192
215
|
logger.log("@beesolve/aws-accounts");
|
|
193
216
|
logger.log("");
|
|
194
217
|
logger.log("Usage:");
|
|
195
218
|
logger.log(
|
|
196
|
-
|
|
219
|
+
` ${cmd} bootstrap [--profile <name>] [--region <region>] [--yes]`
|
|
220
|
+
);
|
|
221
|
+
logger.log(` ${cmd} scan [--profile <name>] [--region <region>]`);
|
|
222
|
+
logger.log(
|
|
223
|
+
` ${cmd} init [--profile <name>] [--region <region>] [--yes]`
|
|
197
224
|
);
|
|
198
|
-
logger.log(" npm run cli -- scan [--profile <name>] [--region <region>]");
|
|
199
225
|
logger.log(
|
|
200
|
-
|
|
226
|
+
` ${cmd} init --update [--profile <name>] [--region <region>] [--yes]`
|
|
201
227
|
);
|
|
228
|
+
logger.log(` ${cmd} regenerate [--yes]`);
|
|
229
|
+
logger.log(` ${cmd} validate`);
|
|
230
|
+
logger.log(` ${cmd} graveyard`);
|
|
231
|
+
logger.log(` ${cmd} graveyard close`);
|
|
202
232
|
logger.log(
|
|
203
|
-
|
|
233
|
+
` ${cmd} profile --sso-start-url <url> [--sso-session <name>] (env: AWS_SSO_START_URL)`
|
|
204
234
|
);
|
|
205
|
-
logger.log(" npm run cli -- regenerate [--yes]");
|
|
206
|
-
logger.log(" npm run cli -- validate");
|
|
207
|
-
logger.log(" npm run cli -- graveyard");
|
|
208
|
-
logger.log(" npm run cli -- graveyard close");
|
|
209
235
|
logger.log(
|
|
210
|
-
|
|
236
|
+
` ${cmd} plan [--profile <name>] [--region <region>] [--refresh]`
|
|
211
237
|
);
|
|
212
238
|
logger.log(
|
|
213
|
-
|
|
239
|
+
` ${cmd} apply [--profile <name>] [--region <region>] [--yes] [--allow-destructive] [--ignore-unsupported]`
|
|
214
240
|
);
|
|
241
|
+
logger.log(` ${cmd} upgrade [--profile <name>] [--region <region>]`);
|
|
215
242
|
logger.log(
|
|
216
|
-
|
|
243
|
+
` ${cmd} drift [--profile <name>] [--region <region>] [--refresh]`
|
|
217
244
|
);
|
|
218
|
-
logger.log(" npm run cli -- upgrade [--profile <name>] [--region <region>]");
|
|
219
245
|
logger.log("");
|
|
220
246
|
logger.log("Environment fallback:");
|
|
221
247
|
logger.log(" AWS_PROFILE, AWS_REGION, AWS_DEFAULT_REGION");
|
package/dist/commands/remote.js
CHANGED
|
@@ -55,7 +55,7 @@ import { assertUnreachable, delay } from "../helpers.js";
|
|
|
55
55
|
import { toPreconditionError } from "../error.js";
|
|
56
56
|
import { sts, organizations, sso, identitystore, s3, logs, account, iam, lambda } from "@beesolve/iam-policy-ts";
|
|
57
57
|
const remoteCommandSchema = v.object({
|
|
58
|
-
subcommand: v.picklist(["bootstrap", "scan", "init", "plan", "apply", "upgrade"]),
|
|
58
|
+
subcommand: v.picklist(["bootstrap", "scan", "init", "plan", "apply", "upgrade", "drift"]),
|
|
59
59
|
profile: v.optional(v.string()),
|
|
60
60
|
region: v.optional(v.string()),
|
|
61
61
|
flags: v.object({
|
|
@@ -657,10 +657,61 @@ async function runRemoteUpgrade(input) {
|
|
|
657
657
|
input.logger.log("");
|
|
658
658
|
input.logger.log("Run init --update to sync your config with new remote features before using plan/apply.");
|
|
659
659
|
}
|
|
660
|
+
async function runRemoteDrift(input) {
|
|
661
|
+
const deployment = await readDeploymentFromContext();
|
|
662
|
+
const baseline = await fetchCurrentState({
|
|
663
|
+
input,
|
|
664
|
+
deployment
|
|
665
|
+
});
|
|
666
|
+
const clientConfig = buildAwsClientConfig({
|
|
667
|
+
profile: input.profile ?? (deployment.profile || void 0),
|
|
668
|
+
region: input.region ?? (deployment.region || void 0)
|
|
669
|
+
});
|
|
670
|
+
const lambdaClient = new LambdaClient(clientConfig);
|
|
671
|
+
input.logger.log("Scanning live AWS state...");
|
|
672
|
+
const result = await invokeLambda({
|
|
673
|
+
lambdaClient,
|
|
674
|
+
lambdaArn: deployment.lambdaArn,
|
|
675
|
+
payload: { action: "scan" }
|
|
676
|
+
});
|
|
677
|
+
if (!result.ok) {
|
|
678
|
+
throw new Error(formatLambdaError(result.error));
|
|
679
|
+
}
|
|
680
|
+
const response = result.response;
|
|
681
|
+
if (!("action" in response) || response.action !== "scan") {
|
|
682
|
+
throw new Error("Unexpected response from Lambda scan action.");
|
|
683
|
+
}
|
|
684
|
+
const liveState = response.state;
|
|
685
|
+
await writeStateCache(cachePath, liveState);
|
|
686
|
+
const plan = diffStates({
|
|
687
|
+
current: baseline,
|
|
688
|
+
next: liveState
|
|
689
|
+
});
|
|
690
|
+
displayDrift({ plan, logger: input.logger });
|
|
691
|
+
}
|
|
692
|
+
function displayDrift(props) {
|
|
693
|
+
const driftOperations = props.plan.operations.filter(
|
|
694
|
+
(operation) => operation.kind !== "provisionIdcPermissionSet"
|
|
695
|
+
);
|
|
696
|
+
if (driftOperations.length === 0 && props.plan.unsupported.length === 0) {
|
|
697
|
+
props.logger.log("No drift.");
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
props.logger.log(`Drift: ${driftOperations.length} change(s) detected since last scan`);
|
|
701
|
+
for (const operation of driftOperations) {
|
|
702
|
+
props.logger.log(formatOperationLine(operation));
|
|
703
|
+
}
|
|
704
|
+
if (props.plan.unsupported.length > 0) {
|
|
705
|
+
props.logger.log("Unsupported diffs:");
|
|
706
|
+
for (const diff of props.plan.unsupported) {
|
|
707
|
+
props.logger.log(` - ${diff.description} [${diff.category}]`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
660
711
|
function warnIfRemotePoliciesNotInConfig(props) {
|
|
661
712
|
const remotePolicies = props.currentState.organization.policies ?? [];
|
|
662
713
|
const hasRemotePolicies = remotePolicies.length > 0;
|
|
663
|
-
const hasLocalPolicies =
|
|
714
|
+
const hasLocalPolicies = props.config.policies.serviceControlPolicies.length > 0 || props.config.policies.resourceControlPolicies.length > 0;
|
|
664
715
|
if (hasRemotePolicies && !hasLocalPolicies) {
|
|
665
716
|
props.logger.log("");
|
|
666
717
|
props.logger.log("Warning: remote state contains SCPs/RCPs not present in your config. Proceeding could delete them.");
|
|
@@ -748,7 +799,7 @@ function displayPlan(props) {
|
|
|
748
799
|
}
|
|
749
800
|
}
|
|
750
801
|
function isDestructiveOperation(operation) {
|
|
751
|
-
return operation.kind === "deleteOu" || operation.kind === "removeAccount" || operation.kind === "deleteIdcUser" || operation.kind === "deleteIdcGroup" || operation.kind === "deleteIdcPermissionSet" || operation.kind === "detachOrgPolicy" || operation.kind === "deleteOrgPolicy";
|
|
802
|
+
return operation.kind === "deleteOu" || operation.kind === "removeAccount" || operation.kind === "deleteIdcUser" || operation.kind === "deleteIdcGroup" || operation.kind === "deleteIdcPermissionSet" || operation.kind === "deleteIdcPermissionSetPermissionsBoundary" || operation.kind === "detachOrgPolicy" || operation.kind === "deleteOrgPolicy" || operation.kind === "deregisterDelegatedAdministrator";
|
|
752
803
|
}
|
|
753
804
|
function formatOperationLine(operation) {
|
|
754
805
|
if (operation.kind === "moveAccount") {
|
|
@@ -826,6 +877,14 @@ function formatOperationLine(operation) {
|
|
|
826
877
|
if (operation.kind === "provisionIdcPermissionSet") {
|
|
827
878
|
return ` provision IdC permission set "${operation.permissionSetName}" to all provisioned accounts`;
|
|
828
879
|
}
|
|
880
|
+
if (operation.kind === "putIdcPermissionSetPermissionsBoundary") {
|
|
881
|
+
const b = operation.permissionsBoundary;
|
|
882
|
+
const label = "managedPolicyArn" in b ? b.managedPolicyArn : `${b.customerManagedPolicyPath}${b.customerManagedPolicyName}`;
|
|
883
|
+
return ` put permissions boundary "${label}" on IdC permission set "${operation.permissionSetName}"`;
|
|
884
|
+
}
|
|
885
|
+
if (operation.kind === "deleteIdcPermissionSetPermissionsBoundary") {
|
|
886
|
+
return ` [destructive] delete permissions boundary from IdC permission set "${operation.permissionSetName}"`;
|
|
887
|
+
}
|
|
829
888
|
if (operation.kind === "removeIdcGroupMembership") {
|
|
830
889
|
return ` remove user "${operation.userName}" from IdC group "${operation.groupDisplayName}"`;
|
|
831
890
|
}
|
|
@@ -866,6 +925,12 @@ function formatOperationLine(operation) {
|
|
|
866
925
|
if (operation.kind === "setIdcAccessControlAttributes") {
|
|
867
926
|
return ` set IdC access control attributes (${operation.attributes.length} attribute(s))`;
|
|
868
927
|
}
|
|
928
|
+
if (operation.kind === "registerDelegatedAdministrator") {
|
|
929
|
+
return ` register delegated administrator "${operation.accountName}" (${operation.accountId}) for ${operation.servicePrincipal}`;
|
|
930
|
+
}
|
|
931
|
+
if (operation.kind === "deregisterDelegatedAdministrator") {
|
|
932
|
+
return ` [destructive] deregister delegated administrator "${operation.accountName}" (${operation.accountId}) for ${operation.servicePrincipal}`;
|
|
933
|
+
}
|
|
869
934
|
assertUnreachable(operation, "Unsupported operation kind in formatOperationLine.");
|
|
870
935
|
}
|
|
871
936
|
function formatPrincipalLabel(principalType, principalName) {
|
|
@@ -1077,6 +1142,7 @@ async function waitForLambdaReady(lambdaClient, functionName) {
|
|
|
1077
1142
|
export {
|
|
1078
1143
|
runRemoteApply,
|
|
1079
1144
|
runRemoteBootstrap,
|
|
1145
|
+
runRemoteDrift,
|
|
1080
1146
|
runRemoteInit,
|
|
1081
1147
|
runRemotePlan,
|
|
1082
1148
|
runRemoteScan,
|
|
@@ -17,6 +17,7 @@ async function runValidateCommand(input) {
|
|
|
17
17
|
checkInlinePolicySizes(config, errors);
|
|
18
18
|
checkOrgPolicySizes(config, errors);
|
|
19
19
|
checkOrgPolicyTargets(config, errors);
|
|
20
|
+
checkDelegatedAdministratorDuplicates(config, errors);
|
|
20
21
|
if (errors.length > 0) {
|
|
21
22
|
for (const error of errors) {
|
|
22
23
|
input.logger.log(`Error: ${error}`);
|
|
@@ -66,7 +67,7 @@ function checkAssignmentPrincipals(config, errors) {
|
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
function checkOrgPolicySizes(config, errors) {
|
|
69
|
-
for (const policy of config.policies
|
|
70
|
+
for (const policy of config.policies.serviceControlPolicies) {
|
|
70
71
|
const contentBytes = Buffer.byteLength(JSON.stringify(policy.content), "utf8");
|
|
71
72
|
if (contentBytes > ORG_POLICY_CONTENT_MAX_BYTES) {
|
|
72
73
|
errors.push(
|
|
@@ -74,7 +75,7 @@ function checkOrgPolicySizes(config, errors) {
|
|
|
74
75
|
);
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
|
-
for (const policy of config.policies
|
|
78
|
+
for (const policy of config.policies.resourceControlPolicies) {
|
|
78
79
|
const contentBytes = Buffer.byteLength(JSON.stringify(policy.content), "utf8");
|
|
79
80
|
if (contentBytes > ORG_POLICY_CONTENT_MAX_BYTES) {
|
|
80
81
|
errors.push(
|
|
@@ -82,13 +83,21 @@ function checkOrgPolicySizes(config, errors) {
|
|
|
82
83
|
);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
86
|
+
for (const policy of config.policies.backupPolicies) {
|
|
87
|
+
const contentBytes = Buffer.byteLength(JSON.stringify(policy.content), "utf8");
|
|
88
|
+
if (contentBytes > ORG_POLICY_CONTENT_MAX_BYTES) {
|
|
89
|
+
errors.push(
|
|
90
|
+
`Backup policy "${policy.name}" content is ${contentBytes} bytes (limit: ${ORG_POLICY_CONTENT_MAX_BYTES}).`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
85
94
|
}
|
|
86
95
|
function checkOrgPolicyTargets(config, errors) {
|
|
87
96
|
const ouNames = new Set(config.organizationalUnits.map((ou) => ou.name));
|
|
88
97
|
const accountNames = new Set(
|
|
89
98
|
config.organizationalUnits.flatMap((ou) => ou.accounts.map((a) => a.name))
|
|
90
99
|
);
|
|
91
|
-
for (const policy of config.policies
|
|
100
|
+
for (const policy of config.policies.serviceControlPolicies) {
|
|
92
101
|
for (const target of policy.targets) {
|
|
93
102
|
if (target !== "root" && !ouNames.has(target) && !accountNames.has(target)) {
|
|
94
103
|
errors.push(
|
|
@@ -97,7 +106,7 @@ function checkOrgPolicyTargets(config, errors) {
|
|
|
97
106
|
}
|
|
98
107
|
}
|
|
99
108
|
}
|
|
100
|
-
for (const policy of config.policies
|
|
109
|
+
for (const policy of config.policies.resourceControlPolicies) {
|
|
101
110
|
for (const target of policy.targets) {
|
|
102
111
|
if (target !== "root" && !ouNames.has(target) && !accountNames.has(target)) {
|
|
103
112
|
errors.push(
|
|
@@ -106,6 +115,27 @@ function checkOrgPolicyTargets(config, errors) {
|
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
}
|
|
118
|
+
for (const policy of config.policies.backupPolicies) {
|
|
119
|
+
for (const target of policy.targets) {
|
|
120
|
+
if (target !== "root" && !ouNames.has(target) && !accountNames.has(target)) {
|
|
121
|
+
errors.push(
|
|
122
|
+
`Backup policy "${policy.name}" references unknown target "${target}".`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function checkDelegatedAdministratorDuplicates(config, errors) {
|
|
129
|
+
const seen = /* @__PURE__ */ new Set();
|
|
130
|
+
for (const da of config.delegatedAdministrators) {
|
|
131
|
+
const key = `${da.account}|${da.servicePrincipal}`;
|
|
132
|
+
if (seen.has(key)) {
|
|
133
|
+
errors.push(
|
|
134
|
+
`Duplicate delegated administrator entry: account "${da.account}" + service principal "${da.servicePrincipal}".`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
seen.add(key);
|
|
138
|
+
}
|
|
109
139
|
}
|
|
110
140
|
function checkInlinePolicySizes(config, errors) {
|
|
111
141
|
for (const ps of config.permissionSets) {
|
package/dist/diff.js
CHANGED
|
@@ -26,22 +26,26 @@ const operationExecutionPriority = {
|
|
|
26
26
|
attachIdcCustomerManagedPolicyReferenceToPermissionSet: 20,
|
|
27
27
|
detachIdcCustomerManagedPolicyReferenceFromPermissionSet: 21,
|
|
28
28
|
provisionIdcPermissionSet: 22,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
29
|
+
putIdcPermissionSetPermissionsBoundary: 23,
|
|
30
|
+
deleteIdcPermissionSetPermissionsBoundary: 24,
|
|
31
|
+
grantIdcAccountAssignment: 25,
|
|
32
|
+
removeIdcGroupMembership: 26,
|
|
33
|
+
revokeIdcAccountAssignment: 27,
|
|
34
|
+
deleteIdcUser: 28,
|
|
35
|
+
deleteIdcGroup: 29,
|
|
36
|
+
deleteIdcPermissionSet: 30,
|
|
37
|
+
deleteOu: 31,
|
|
38
|
+
createOrgPolicy: 32,
|
|
39
|
+
updateOrgPolicyContent: 33,
|
|
40
|
+
updateOrgPolicyDescription: 34,
|
|
41
|
+
attachOrgPolicy: 35,
|
|
42
|
+
detachOrgPolicy: 36,
|
|
43
|
+
deleteOrgPolicy: 37,
|
|
44
|
+
putAlternateContact: 38,
|
|
45
|
+
deleteAlternateContact: 39,
|
|
46
|
+
setIdcAccessControlAttributes: 40,
|
|
47
|
+
registerDelegatedAdministrator: 41,
|
|
48
|
+
deregisterDelegatedAdministrator: 42
|
|
45
49
|
};
|
|
46
50
|
function diffStates(props) {
|
|
47
51
|
const operations = [];
|
|
@@ -56,6 +60,11 @@ function diffStates(props) {
|
|
|
56
60
|
const nextAccountIds = new Set(
|
|
57
61
|
nextOrganization.accounts.filter((account) => account.id !== pendingCreationId).map((account) => account.id)
|
|
58
62
|
);
|
|
63
|
+
const ouNamesBeingCreated = new Set(
|
|
64
|
+
nextOrganization.organizationalUnits.filter(
|
|
65
|
+
(ou) => !currentOrganization.organizationalUnitByName.has(ou.name)
|
|
66
|
+
).map((ou) => ou.name)
|
|
67
|
+
);
|
|
59
68
|
for (const nextAccount of nextOrganization.accounts) {
|
|
60
69
|
const currentAccount = nextAccount.id !== pendingCreationId ? currentOrganization.accountById.get(nextAccount.id) : void 0;
|
|
61
70
|
if (currentAccount == null) {
|
|
@@ -70,11 +79,21 @@ function diffStates(props) {
|
|
|
70
79
|
organizationalUnitNameById: nextOrganization.organizationalUnitNameById,
|
|
71
80
|
organizationalUnitId: nextAccount.parentId
|
|
72
81
|
}) === false) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
if (ouNamesBeingCreated.has(targetOuName)) {
|
|
83
|
+
operations.push({
|
|
84
|
+
kind: "createAccount",
|
|
85
|
+
accountName: nextAccount.name,
|
|
86
|
+
accountEmail: nextAccount.email,
|
|
87
|
+
targetOuId: nextAccount.parentId,
|
|
88
|
+
targetOuName
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
unsupported.push({
|
|
92
|
+
kind: "newAccountWithUnknownOu",
|
|
93
|
+
category: "unsupportedMutation",
|
|
94
|
+
description: `new account "${nextAccount.name}" has unresolved target OU "${targetOuName}" (${nextAccount.parentId})`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
78
97
|
continue;
|
|
79
98
|
}
|
|
80
99
|
operations.push({
|
|
@@ -117,7 +136,7 @@ function diffStates(props) {
|
|
|
117
136
|
});
|
|
118
137
|
continue;
|
|
119
138
|
}
|
|
120
|
-
if (currentAccount.id === pendingCreationId || nextAccount.id === pendingCreationId || currentAccount.parentId === pendingCreationId
|
|
139
|
+
if (currentAccount.id === pendingCreationId || nextAccount.id === pendingCreationId || currentAccount.parentId === pendingCreationId) {
|
|
121
140
|
continue;
|
|
122
141
|
}
|
|
123
142
|
const fromOuName = resolveOrganizationalUnitName({
|
|
@@ -130,6 +149,41 @@ function diffStates(props) {
|
|
|
130
149
|
rootId: nextOrganization.rootId,
|
|
131
150
|
organizationalUnitId: nextAccount.parentId
|
|
132
151
|
});
|
|
152
|
+
if (nextAccount.parentId === pendingCreationId) {
|
|
153
|
+
if (ouNamesBeingCreated.has(toOuName)) {
|
|
154
|
+
if (currentAccount.name !== nextAccount.name) {
|
|
155
|
+
operations.push({
|
|
156
|
+
kind: "updateAccountName",
|
|
157
|
+
accountId: nextAccount.id,
|
|
158
|
+
fromAccountName: currentAccount.name,
|
|
159
|
+
toAccountName: nextAccount.name
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
operations.push({
|
|
163
|
+
kind: "moveAccount",
|
|
164
|
+
accountId: nextAccount.id,
|
|
165
|
+
accountName: nextAccount.name,
|
|
166
|
+
fromOuId: currentAccount.parentId,
|
|
167
|
+
fromOuName,
|
|
168
|
+
toOuId: nextAccount.parentId,
|
|
169
|
+
toOuName
|
|
170
|
+
});
|
|
171
|
+
diffAlternateContacts({
|
|
172
|
+
operations,
|
|
173
|
+
accountId: nextAccount.id,
|
|
174
|
+
accountName: nextAccount.name,
|
|
175
|
+
currentContacts: currentAccount.alternateContacts ?? [],
|
|
176
|
+
nextContacts: nextAccount.alternateContacts ?? []
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
unsupported.push({
|
|
180
|
+
kind: "existingAccountWithUnknownTargetOu",
|
|
181
|
+
category: "unsupportedMutation",
|
|
182
|
+
description: `existing account "${nextAccount.name}" has unresolved target OU "${toOuName}" (${nextAccount.parentId})`
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
133
187
|
if (currentAccount.name !== nextAccount.name) {
|
|
134
188
|
operations.push({
|
|
135
189
|
kind: "updateAccountName",
|
|
@@ -306,11 +360,20 @@ function diffStates(props) {
|
|
|
306
360
|
organizationalUnitNameById: nextOrganization.organizationalUnitNameById,
|
|
307
361
|
organizationalUnitId: addedOrganizationalUnit.parentId
|
|
308
362
|
}) === false) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
363
|
+
if (ouNamesBeingCreated.has(parentOuName) && parentOuName !== addedOrganizationalUnit.name) {
|
|
364
|
+
operations.push({
|
|
365
|
+
kind: "createOu",
|
|
366
|
+
ouName: addedOrganizationalUnit.name,
|
|
367
|
+
parentOuId: addedOrganizationalUnit.parentId,
|
|
368
|
+
parentOuName
|
|
369
|
+
});
|
|
370
|
+
} else {
|
|
371
|
+
unsupported.push({
|
|
372
|
+
kind: "newOuWithUnknownParent",
|
|
373
|
+
category: "unsupportedMutation",
|
|
374
|
+
description: `new OU "${addedOrganizationalUnit.name}" has unresolved parent "${parentOuName}" (${addedOrganizationalUnit.parentId})`
|
|
375
|
+
});
|
|
376
|
+
}
|
|
314
377
|
continue;
|
|
315
378
|
}
|
|
316
379
|
operations.push({
|
|
@@ -576,6 +639,23 @@ function diffStates(props) {
|
|
|
576
639
|
customerManagedPolicyPath: customerManagedPolicy.path
|
|
577
640
|
});
|
|
578
641
|
}
|
|
642
|
+
const currentBoundary = currentPermissionSet?.permissionsBoundary ?? null;
|
|
643
|
+
const nextBoundary = nextPermissionSet.permissionsBoundary ?? null;
|
|
644
|
+
if (nextBoundary != null) {
|
|
645
|
+
if (currentBoundary == null || JSON.stringify(currentBoundary) !== JSON.stringify(nextBoundary)) {
|
|
646
|
+
operations.push({
|
|
647
|
+
kind: "putIdcPermissionSetPermissionsBoundary",
|
|
648
|
+
permissionSetName: nextPermissionSet.name,
|
|
649
|
+
permissionsBoundary: nextBoundary
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (nextBoundary == null && currentBoundary != null) {
|
|
654
|
+
operations.push({
|
|
655
|
+
kind: "deleteIdcPermissionSetPermissionsBoundary",
|
|
656
|
+
permissionSetName: nextPermissionSet.name
|
|
657
|
+
});
|
|
658
|
+
}
|
|
579
659
|
if (currentPermissionSet != null && operations.length > permissionSetMutationStartIndex && permissionSetNamesWithDesiredAssignments.has(nextPermissionSet.name)) {
|
|
580
660
|
operations.push({
|
|
581
661
|
kind: "provisionIdcPermissionSet",
|
|
@@ -648,6 +728,54 @@ function diffStates(props) {
|
|
|
648
728
|
attributes: nextAccessControlAttributes
|
|
649
729
|
});
|
|
650
730
|
}
|
|
731
|
+
const nextAccountNameById = new Map(
|
|
732
|
+
props.next.organization.accounts.map((account) => [
|
|
733
|
+
account.id,
|
|
734
|
+
account.name
|
|
735
|
+
])
|
|
736
|
+
);
|
|
737
|
+
const currentAccountNameById = new Map(
|
|
738
|
+
props.current.organization.accounts.map((account) => [
|
|
739
|
+
account.id,
|
|
740
|
+
account.name
|
|
741
|
+
])
|
|
742
|
+
);
|
|
743
|
+
const currentDelegatedAdmins = props.current.organization.delegatedAdministrators ?? [];
|
|
744
|
+
const nextDelegatedAdmins = props.next.organization.delegatedAdministrators ?? [];
|
|
745
|
+
const nextDelegatedAdminKeys = new Set(
|
|
746
|
+
nextDelegatedAdmins.map((da) => `${da.accountId}|${da.servicePrincipal}`)
|
|
747
|
+
);
|
|
748
|
+
const currentDelegatedAdminKeys = new Set(
|
|
749
|
+
currentDelegatedAdmins.map(
|
|
750
|
+
(da) => `${da.accountId}|${da.servicePrincipal}`
|
|
751
|
+
)
|
|
752
|
+
);
|
|
753
|
+
for (const nextDa of nextDelegatedAdmins) {
|
|
754
|
+
if (currentDelegatedAdminKeys.has(
|
|
755
|
+
`${nextDa.accountId}|${nextDa.servicePrincipal}`
|
|
756
|
+
)) {
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
operations.push({
|
|
760
|
+
kind: "registerDelegatedAdministrator",
|
|
761
|
+
accountId: nextDa.accountId,
|
|
762
|
+
accountName: nextAccountNameById.get(nextDa.accountId) ?? nextDa.accountId,
|
|
763
|
+
servicePrincipal: nextDa.servicePrincipal
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
for (const currentDa of currentDelegatedAdmins) {
|
|
767
|
+
if (nextDelegatedAdminKeys.has(
|
|
768
|
+
`${currentDa.accountId}|${currentDa.servicePrincipal}`
|
|
769
|
+
)) {
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
operations.push({
|
|
773
|
+
kind: "deregisterDelegatedAdministrator",
|
|
774
|
+
accountId: currentDa.accountId,
|
|
775
|
+
accountName: currentAccountNameById.get(currentDa.accountId) ?? currentDa.accountId,
|
|
776
|
+
servicePrincipal: currentDa.servicePrincipal
|
|
777
|
+
});
|
|
778
|
+
}
|
|
651
779
|
const currentPolicies = props.current.organization.policies ?? [];
|
|
652
780
|
const nextPolicies = props.next.organization.policies ?? [];
|
|
653
781
|
const currentPolicyAttachments = props.current.organization.policyAttachments ?? [];
|
|
@@ -699,23 +827,22 @@ function diffStates(props) {
|
|
|
699
827
|
const nextOuNameById = new Map(
|
|
700
828
|
props.next.organization.organizationalUnits.map((ou) => [ou.id, ou.name])
|
|
701
829
|
);
|
|
702
|
-
const nextAccountNameById = new Map(
|
|
703
|
-
props.next.organization.accounts.map((account) => [account.id, account.name])
|
|
704
|
-
);
|
|
705
830
|
const currentOuNameById = new Map(
|
|
706
|
-
props.current.organization.organizationalUnits.map((ou) => [
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
831
|
+
props.current.organization.organizationalUnits.map((ou) => [
|
|
832
|
+
ou.id,
|
|
833
|
+
ou.name
|
|
834
|
+
])
|
|
710
835
|
);
|
|
711
836
|
function resolveNextTargetName(targetId, targetType) {
|
|
712
837
|
if (targetType === "ROOT") return "root";
|
|
713
|
-
if (targetType === "ORGANIZATIONAL_UNIT")
|
|
838
|
+
if (targetType === "ORGANIZATIONAL_UNIT")
|
|
839
|
+
return nextOuNameById.get(targetId) ?? "unknown";
|
|
714
840
|
return nextAccountNameById.get(targetId) ?? "unknown";
|
|
715
841
|
}
|
|
716
842
|
function resolveCurrentTargetName(targetId, targetType) {
|
|
717
843
|
if (targetType === "ROOT") return "root";
|
|
718
|
-
if (targetType === "ORGANIZATIONAL_UNIT")
|
|
844
|
+
if (targetType === "ORGANIZATIONAL_UNIT")
|
|
845
|
+
return currentOuNameById.get(targetId) ?? "unknown";
|
|
719
846
|
return currentAccountNameById.get(targetId) ?? "unknown";
|
|
720
847
|
}
|
|
721
848
|
for (const nextAttachment of nextPolicyAttachments) {
|
|
@@ -796,6 +923,27 @@ function diffStates(props) {
|
|
|
796
923
|
}
|
|
797
924
|
return getOperationSortKey(left).localeCompare(getOperationSortKey(right));
|
|
798
925
|
});
|
|
926
|
+
const createOuOps = operations.filter(
|
|
927
|
+
(op) => op.kind === "createOu"
|
|
928
|
+
);
|
|
929
|
+
const otherOps = operations.filter((op) => op.kind !== "createOu");
|
|
930
|
+
if (createOuOps.some((op) => op.parentOuId === pendingCreationId)) {
|
|
931
|
+
let visitOu2 = function(op) {
|
|
932
|
+
if (visited.has(op.ouName)) return;
|
|
933
|
+
visited.add(op.ouName);
|
|
934
|
+
if (op.parentOuId === pendingCreationId) {
|
|
935
|
+
const parent = createOuByName.get(op.parentOuName);
|
|
936
|
+
if (parent != null) visitOu2(parent);
|
|
937
|
+
}
|
|
938
|
+
topoSorted.push(op);
|
|
939
|
+
};
|
|
940
|
+
var visitOu = visitOu2;
|
|
941
|
+
const createOuByName = new Map(createOuOps.map((op) => [op.ouName, op]));
|
|
942
|
+
const visited = /* @__PURE__ */ new Set();
|
|
943
|
+
const topoSorted = [];
|
|
944
|
+
for (const op of createOuOps) visitOu2(op);
|
|
945
|
+
operations.splice(0, operations.length, ...topoSorted, ...otherOps);
|
|
946
|
+
}
|
|
799
947
|
unsupported.sort((left, right) => {
|
|
800
948
|
const kindComparison = left.kind.localeCompare(right.kind);
|
|
801
949
|
if (kindComparison !== 0) {
|
|
@@ -1037,6 +1185,13 @@ function getOperationSortKey(operation) {
|
|
|
1037
1185
|
if (operation.kind === "setIdcAccessControlAttributes") {
|
|
1038
1186
|
return operation.kind;
|
|
1039
1187
|
}
|
|
1188
|
+
if (operation.kind === "registerDelegatedAdministrator" || operation.kind === "deregisterDelegatedAdministrator") {
|
|
1189
|
+
return [
|
|
1190
|
+
operation.kind,
|
|
1191
|
+
operation.accountName,
|
|
1192
|
+
operation.servicePrincipal
|
|
1193
|
+
].join("|");
|
|
1194
|
+
}
|
|
1040
1195
|
return "zzzz";
|
|
1041
1196
|
}
|
|
1042
1197
|
function normalizeJsonContent(content) {
|