@go-to-k/cdkd 0.0.3 → 0.0.4

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.
Binary file
package/dist/index.js CHANGED
@@ -3132,6 +3132,11 @@ var TemplateParser = class {
3132
3132
  // src/analyzer/dag-builder.ts
3133
3133
  import graphlib from "graphlib";
3134
3134
  var { Graph, alg } = graphlib;
3135
+ var IAM_ROLE_POLICY_TYPES = /* @__PURE__ */ new Set([
3136
+ "AWS::IAM::Policy",
3137
+ "AWS::IAM::RolePolicy",
3138
+ "AWS::IAM::ManagedPolicy"
3139
+ ]);
3135
3140
  var DagBuilder = class {
3136
3141
  logger = getLogger().child("DagBuilder");
3137
3142
  parser = new TemplateParser();
@@ -3172,6 +3177,7 @@ var DagBuilder = class {
3172
3177
  }
3173
3178
  }
3174
3179
  this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);
3180
+ edgeCount += this.addCustomResourcePolicyEdges(graph, template);
3175
3181
  if (!alg.isAcyclic(graph)) {
3176
3182
  const cycles = this.findCycles(graph);
3177
3183
  throw new DependencyError(
@@ -3314,6 +3320,123 @@ var DagBuilder = class {
3314
3320
  const deps = this.getAllDependencies(graph, resourceA);
3315
3321
  return deps.has(resourceB);
3316
3322
  }
3323
+ /**
3324
+ * Add implicit edges from IAM::Policy resources to Custom Resources whose
3325
+ * ServiceToken Lambda's execution role those policies attach to.
3326
+ *
3327
+ * Returns the number of edges added.
3328
+ */
3329
+ addCustomResourcePolicyEdges(graph, template) {
3330
+ const rolePolicies = this.buildRolePoliciesMap(template);
3331
+ if (rolePolicies.size === 0) {
3332
+ return 0;
3333
+ }
3334
+ let added = 0;
3335
+ for (const logicalId of this.parser.getResourceIds(template)) {
3336
+ const resource = this.parser.getResource(template, logicalId);
3337
+ if (!resource || !this.isCustomResourceType(resource.Type)) {
3338
+ continue;
3339
+ }
3340
+ const serviceToken = (resource.Properties ?? {})["ServiceToken"];
3341
+ const lambdaId = this.extractLogicalIdFromReference(serviceToken);
3342
+ if (!lambdaId)
3343
+ continue;
3344
+ const lambdaResource = this.parser.getResource(template, lambdaId);
3345
+ if (!lambdaResource || lambdaResource.Type !== "AWS::Lambda::Function") {
3346
+ continue;
3347
+ }
3348
+ const roleId = this.extractLogicalIdFromReference((lambdaResource.Properties ?? {})["Role"]);
3349
+ if (!roleId)
3350
+ continue;
3351
+ const policies = rolePolicies.get(roleId);
3352
+ if (!policies)
3353
+ continue;
3354
+ for (const policyId of policies) {
3355
+ if (policyId === logicalId)
3356
+ continue;
3357
+ if (!graph.hasNode(policyId))
3358
+ continue;
3359
+ if (graph.hasEdge(policyId, logicalId))
3360
+ continue;
3361
+ graph.setEdge(policyId, logicalId);
3362
+ added++;
3363
+ this.logger.debug(
3364
+ `Added implicit edge (custom resource policy): ${policyId} -> ${logicalId}`
3365
+ );
3366
+ }
3367
+ }
3368
+ if (added > 0) {
3369
+ this.logger.debug(`Added ${added} implicit edges for custom resource policies`);
3370
+ }
3371
+ return added;
3372
+ }
3373
+ isCustomResourceType(type) {
3374
+ return type === "AWS::CloudFormation::CustomResource" || type.startsWith("Custom::");
3375
+ }
3376
+ /**
3377
+ * Build a map of roleLogicalId -> Set<policyLogicalId> by scanning the
3378
+ * template for IAM::Policy / IAM::RolePolicy / IAM::ManagedPolicy resources
3379
+ * that attach to a role by Ref/GetAtt.
3380
+ */
3381
+ buildRolePoliciesMap(template) {
3382
+ const map = /* @__PURE__ */ new Map();
3383
+ for (const [policyId, resource] of Object.entries(template.Resources)) {
3384
+ if (!IAM_ROLE_POLICY_TYPES.has(resource.Type))
3385
+ continue;
3386
+ for (const roleId of this.extractAttachedRoleIds(resource)) {
3387
+ let set = map.get(roleId);
3388
+ if (!set) {
3389
+ set = /* @__PURE__ */ new Set();
3390
+ map.set(roleId, set);
3391
+ }
3392
+ set.add(policyId);
3393
+ }
3394
+ }
3395
+ return map;
3396
+ }
3397
+ /**
3398
+ * Extract the logical IDs of IAM::Role resources that a policy resource
3399
+ * attaches to. Supports both `Roles: [Ref]` (IAM::Policy / IAM::ManagedPolicy)
3400
+ * and `RoleName: Ref` (IAM::RolePolicy) shapes.
3401
+ */
3402
+ extractAttachedRoleIds(resource) {
3403
+ const ids = [];
3404
+ const props = resource.Properties ?? {};
3405
+ const roles = props["Roles"];
3406
+ if (Array.isArray(roles)) {
3407
+ for (const entry of roles) {
3408
+ const id = this.extractLogicalIdFromReference(entry);
3409
+ if (id)
3410
+ ids.push(id);
3411
+ }
3412
+ }
3413
+ const roleName = props["RoleName"];
3414
+ const roleNameId = this.extractLogicalIdFromReference(roleName);
3415
+ if (roleNameId)
3416
+ ids.push(roleNameId);
3417
+ return ids;
3418
+ }
3419
+ /**
3420
+ * Extract a resource logical ID from a direct Ref or Fn::GetAtt expression.
3421
+ * Returns undefined for literals or intrinsics we can't statically resolve
3422
+ * (Fn::Join, Fn::ImportValue, etc.) — callers should skip in that case.
3423
+ */
3424
+ extractLogicalIdFromReference(value) {
3425
+ if (typeof value !== "object" || value === null)
3426
+ return void 0;
3427
+ const obj = value;
3428
+ if ("Ref" in obj && typeof obj["Ref"] === "string") {
3429
+ const ref = obj["Ref"];
3430
+ return ref.startsWith("AWS::") ? void 0 : ref;
3431
+ }
3432
+ if ("Fn::GetAtt" in obj) {
3433
+ const getAtt = obj["Fn::GetAtt"];
3434
+ if (Array.isArray(getAtt) && typeof getAtt[0] === "string") {
3435
+ return getAtt[0];
3436
+ }
3437
+ }
3438
+ return void 0;
3439
+ }
3317
3440
  };
3318
3441
 
3319
3442
  // src/analyzer/replacement-rules.ts