Skip to content

Generalize SageMaker launcher IAM roles for classifier and segmentation#691

Merged
gnieuwenhuis merged 14 commits into
devfrom
sagemaker-launcher-iac
May 25, 2026
Merged

Generalize SageMaker launcher IAM roles for classifier and segmentation#691
gnieuwenhuis merged 14 commits into
devfrom
sagemaker-launcher-iac

Conversation

@gnieuwenhuis
Copy link
Copy Markdown
Contributor

@gnieuwenhuis gnieuwenhuis commented May 25, 2026

As part of data-mermaid/mermaid-classifier#43, we are setting up generalized access so that data scientists can submit Sagemaker training and processing jobs as needed from their local machines.

The setup matches what was already being done for the mermaid-classifier, except generalizes it so that the same IAM Roles can be used for both the classifier and the segmentation models.

Included in this PR is also a launcher_convention document that outlines the contract which is to be used by both repos. Since we don't have a single souce of truth for this document to live in, I put it here alongside the permissions changes so that both models can reference it here.

Summary by CodeRabbit

  • Chores

    • Restructured SageMaker infrastructure to support both classifier and segmentation job launchers with a unified launcher role, separate container image repositories, and enhanced IAM permissions covering both job types and logging.
  • Documentation

    • Added SageMaker launcher conventions guide documenting shared standards for launcher implementation, configuration schemas, invocation requirements, and deployment checklist.

Review Change Stack

gnieuwenhuis and others added 13 commits May 25, 2026 11:31
Adds create_segmentation_jobs_repo() method and wires it into __init__,
provisioning a mermaid-segmentation-jobs ECR repo with image scanning,
RETAIN removal policy, 10-image lifecycle rule, and a CfnOutput export.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renames `create_classifier_training_repo` → `create_classifier_jobs_repo`,
construct ID `MermaidClassifierTrainingRepo` → `MermaidClassifierJobsRepo`,
`repository_name` `mermaid-classifier-training` → `mermaid-classifier-jobs`,
CfnOutput logical ID/export `MermaidClassifierTrainingRepoUri` →
`MermaidClassifierJobsRepoUri`, and `self.classifier_training_repo` →
`self.classifier_jobs_repo`. The old repo has RETAIN policy so it will be
orphaned (not deleted) and old images remain accessible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rename inline-policy ID MermaidClassifierLauncherPassRolePolicy →
MermaidSagemakerLauncherPassRolePolicy so all four inline policies on
the launcher role use the same MermaidSagemakerLauncher* prefix.
Also update the adjacent comment from CreateTrainingJob to
CreateTraining/ProcessingJob to reflect the role's actual scope.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

Warning

Review limit reached

@gnieuwenhuis, we couldn't start this review because you've used your available PR reviews for now.

Your plan includes 1 review of capacity. Refill in 39 minutes and 34 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b3b22afc-fc96-46a9-923e-25eadd2f80bc

📥 Commits

Reviewing files that changed from the base of the PR and between 59e64f2 and 31a16e6.

📒 Files selected for processing (2)
  • iac/sagemaker-launcher-convention.md
  • iac/stacks/sagemaker.py
📝 Walkthrough

Walkthrough

The PR consolidates SageMaker launcher infrastructure by replacing classifier-specific training roles and repositories with shared launcher role and dual ECR repositories (classifier and segmentation jobs), updating IAM policies to cover both job types with CloudWatch observability, and adding launcher conventions documentation as a cross-repo contract.

Changes

SageMaker launcher infrastructure consolidation

Layer / File(s) Summary
ECR repositories for classifier and segmentation jobs
iac/stacks/sagemaker.py
Constructor wiring and factory methods create separate mermaid-classifier-jobs and mermaid-segmentation-jobs repositories under a shared "mermaid-*-jobs" naming convention.
Shared launcher role with ECR access
iac/stacks/sagemaker.py
New MermaidSagemakerLauncherRole replaces the classifier-specific launcher role and is granted push/pull/manage access to both job ECR repositories.
SageMaker job and observability permissions
iac/stacks/sagemaker.py
Launcher role is granted SageMaker Training and Processing job submission/observation, CloudWatch log tailing including StartLiveTail, and PassRole delegation to the SageMaker execution role.
Stack integration and suppressions
iac/stacks/sagemaker.py, iac/nag_suppressions.py
Execution role comment is updated, stack output exports the shared launcher role ARN, and CDK NAG suppressions target the new shared launcher role construct paths and scoped resource access.
Launcher conventions documentation
iac/sagemaker-launcher-convention.md
New contract document defines AWS account/profile setup, YAML schema per job type, non-overridable launcher invariants, CLI invocation shape, required outputs, ECR tagging, MLflow app mapping, and PR checklist.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • data-mermaid/mermaid-api#661: Introduced the original iac/nag_suppressions.py module and suppression framework that this PR updates for the new shared launcher role.
  • data-mermaid/mermaid-api#686: Also modifies classifier launcher and training IAM roles and ECR/SageMaker resource scoping in both nag_suppressions.py and sagemaker.py.

Suggested labels

enhancement

Suggested reviewers

  • ms280690
  • michaelconnor00
  • saanobhaai
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main objective: generalizing SageMaker launcher IAM roles to support both classifier and segmentation use cases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sagemaker-launcher-iac

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gnieuwenhuis gnieuwenhuis requested review from Copilot and ms280690 May 25, 2026 18:17
@gnieuwenhuis gnieuwenhuis marked this pull request as ready for review May 25, 2026 18:18
@github-actions
Copy link
Copy Markdown

cdk-nag report

No unsuppressed errors.


See iac/nag_suppressions.py to add suppressions for accepted risks.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the IaC for SageMaker “launcher” workflows so a single shared operator role can submit both Training and Processing jobs for mermaid-classifier and mermaid-segmentation, and adds a cross-repo convention document describing the launcher contract.

Changes:

  • Provision a new mermaid-segmentation-jobs ECR repo and rename the classifier ECR repo to mermaid-classifier-jobs.
  • Replace the classifier-only launcher role with a shared mermaid-sagemaker-launcher-role that includes ProcessingJob + CloudWatch Logs permissions.
  • Add iac/sagemaker-launcher-convention.md documenting CLI/YAML/role/ECR tagging conventions intended to be used by both model repos.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
iac/stacks/sagemaker.py Adds segmentation ECR repo, renames classifier repo, and creates a shared launcher role with expanded SageMaker + Logs permissions.
iac/sagemaker-launcher-convention.md Adds cross-repo contract for launcher scripts, including IAM/SSO setup, YAML schema, and tagging conventions.
iac/nag_suppressions.py Updates cdk-nag suppression paths/reasons to match the renamed shared launcher role and its inline policies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread iac/stacks/sagemaker.py
Comment thread iac/sagemaker-launcher-convention.md Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

cdk diff ✅ Success

Show Output
start: Building GithubAccess Template
success: Built GithubAccess Template
start: Publishing GithubAccess Template (554812291621-us-east-1-403885e2)
success: Published GithubAccess Template (554812291621-us-east-1-403885e2)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)

Stack GithubAccess
There were no differences

start: Building mermaid-api-infra-common Template
success: Built mermaid-api-infra-common Template
start: Publishing mermaid-api-infra-common Template (554812291621-us-east-1-37993ba8)
success: Published mermaid-api-infra-common Template (554812291621-us-east-1-37993ba8)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)

Stack mermaid-api-infra-common
There were no differences

start: Building dev-mermaid-static-site Template
success: Built dev-mermaid-static-site Template
start: Publishing dev-mermaid-static-site Template (554812291621-us-east-1-705b66ee)
success: Published dev-mermaid-static-site Template (554812291621-us-east-1-705b66ee)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)

Stack dev-mermaid-static-site
There were no differences

start: Building dev-mermaid-api-django Template
success: Built dev-mermaid-api-django Template
start: Publishing dev-mermaid-api-django Template (554812291621-us-east-1-d02f2e92)
success: Published dev-mermaid-api-django Template (554812291621-us-east-1-d02f2e92)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)

Stack dev-mermaid-api-django
Resources
[~] AWS::ECS::TaskDefinition ScheduledBackupTaskDef ScheduledBackupTaskDef48789D5A replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -147,7 +147,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:c73c7c0c9813806214375571c0240ba33daedf4f4a45076a326ea8fc6a7ec9a2"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",
[~] AWS::ECS::TaskDefinition SummaryCacheTaskDef SummaryCacheTaskDefFAAC683D replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -151,7 +151,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:c73c7c0c9813806214375571c0240ba33daedf4f4a45076a326ea8fc6a7ec9a2"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",
[~] AWS::ECS::TaskDefinition ApiTaskDefinition ApiTaskDefinition51EA709E replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -145,7 +145,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:c73c7c0c9813806214375571c0240ba33daedf4f4a45076a326ea8fc6a7ec9a2"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",
[~] AWS::ECS::TaskDefinition General/Worker/QueueProcessingTaskDef GeneralWorkerQueueProcessingTaskDef1C2A1522 replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -158,7 +158,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:c73c7c0c9813806214375571c0240ba33daedf4f4a45076a326ea8fc6a7ec9a2"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",
[~] AWS::ECS::TaskDefinition ImageProcess/Worker/QueueProcessingTaskDef ImageProcessWorkerQueueProcessingTaskDefACA5B138 replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -158,7 +158,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:c73c7c0c9813806214375571c0240ba33daedf4f4a45076a326ea8fc6a7ec9a2"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",


start: Building dev-mermaid-sagemaker Template
success: Built dev-mermaid-sagemaker Template
start: Publishing dev-mermaid-sagemaker Template (554812291621-us-east-1-22b36f85)
success: Published dev-mermaid-sagemaker Template (554812291621-us-east-1-22b36f85)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)

Stack dev-mermaid-sagemaker
IAM Statement Changes
┌───┬──────────────────────────────────────────────────────────────────────────────┬────────┬───────────────────────────────────────┬───────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                                                     │ Effect │ Action                                │ Principal                                             │ Condition                                                                                                             │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${devDataBucket.Arn}                                                         │ Allow  │ s3:Abort*                             │ AWS:${devMermaidSagemakerLauncherRole}                │                                                                                                                       │
│   │ ${devDataBucket.Arn}/runs/*                                                  │        │ s3:DeleteObject*                      │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:GetBucket*                         │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:GetObject*                         │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:List*                              │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObject                          │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObjectLegalHold                 │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObjectRetention                 │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObjectTagging                   │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObjectVersionTagging            │                                                       │                                                                                                                       │
│ - │ ${devDataBucket.Arn}                                                         │ Allow  │ s3:Abort*                             │ AWS:${devMermaidClassifierLauncherRole}               │                                                                                                                       │
│   │ ${devDataBucket.Arn}/runs/*                                                  │        │ s3:DeleteObject*                      │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:GetBucket*                         │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:GetObject*                         │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:List*                              │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObject                          │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObjectLegalHold                 │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObjectRetention                 │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObjectTagging                   │                                                       │                                                                                                                       │
│   │                                                                              │        │ s3:PutObjectVersionTagging            │                                                       │                                                                                                                       │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ - │ ${devMermaidClassifierLauncherRole.Arn}                                      │ Allow  │ sts:AssumeRole                        │ AWS:arn:${AWS::Partition}:iam::${AWS::AccountId}:root │ "ArnLike": {                                                                                                          │
│   │                                                                              │        │                                       │                                                       │   "aws:PrincipalArn": "arn:aws:iam::${AWS::AccountId}:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_SageMaker_*" │
│   │                                                                              │        │                                       │                                                       │ }                                                                                                                     │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${devMermaidSagemakerLauncherRole.Arn}                                       │ Allow  │ sts:AssumeRole                        │ AWS:arn:${AWS::Partition}:iam::${AWS::AccountId}:root │ "ArnLike": {                                                                                                          │
│   │                                                                              │        │                                       │                                                       │   "aws:PrincipalArn": "arn:aws:iam::${AWS::AccountId}:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_SageMaker_*" │
│   │                                                                              │        │                                       │                                                       │ }                                                                                                                     │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${devSagemakerExecutionRole.Arn}                                             │ Allow  │ iam:PassRole                          │ AWS:${devMermaidSagemakerLauncherRole}                │ "StringEquals": {                                                                                                     │
│   │                                                                              │        │                                       │                                                       │   "iam:PassedToService": "sagemaker.amazonaws.com"                                                                    │
│   │                                                                              │        │                                       │                                                       │ }                                                                                                                     │
│ - │ ${devSagemakerExecutionRole.Arn}                                             │ Allow  │ iam:PassRole                          │ AWS:${devMermaidClassifierLauncherRole}               │ "StringEquals": {                                                                                                     │
│   │                                                                              │        │                                       │                                                       │   "iam:PassedToService": "sagemaker.amazonaws.com"                                                                    │
│   │                                                                              │        │                                       │                                                       │ }                                                                                                                     │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ *                                                                            │ Allow  │ ecr:GetAuthorizationToken             │ AWS:${devMermaidSagemakerLauncherRole}                │                                                                                                                       │
│ + │ *                                                                            │ Allow  │ sagemaker-mlflow:*                    │ AWS:${devMermaidSagemakerLauncherRole}                │                                                                                                                       │
│   │                                                                              │        │ sagemaker:CreatePresignedMlflowAppUrl │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:DescribeMlflowApp           │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:ListMlflowApps              │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:ListProcessingJobs          │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:ListTrainingJobs            │                                                       │                                                                                                                       │
│ + │ *                                                                            │ Allow  │ logs:DescribeLogGroups                │ AWS:${devMermaidSagemakerLauncherRole}                │                                                                                                                       │
│ - │ *                                                                            │ Allow  │ ecr:GetAuthorizationToken             │ AWS:${devMermaidClassifierLauncherRole}               │                                                                                                                       │
│ - │ *                                                                            │ Allow  │ sagemaker-mlflow:*                    │ AWS:${devMermaidClassifierLauncherRole}               │                                                                                                                       │
│   │                                                                              │        │ sagemaker:CreatePresignedMlflowAppUrl │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:DescribeMlflowApp           │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:ListMlflowApps              │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:ListTrainingJobs            │                                                       │                                                                                                                       │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/mermaid-*-jobs       │ Allow  │ ecr:BatchCheckLayerAvailability       │ AWS:${devMermaidSagemakerLauncherRole}                │                                                                                                                       │
│   │                                                                              │        │ ecr:BatchDeleteImage                  │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:BatchGetImage                     │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:CompleteLayerUpload               │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:DescribeImages                    │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:DescribeRepositories              │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:GetDownloadUrlForLayer            │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:InitiateLayerUpload               │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:ListImages                        │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:PutImage                          │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:UploadLayerPart                   │                                                       │                                                                                                                       │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ - │ arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/mermaid-classifier-* │ Allow  │ ecr:BatchCheckLayerAvailability       │ AWS:${devMermaidClassifierLauncherRole}               │                                                                                                                       │
│   │                                                                              │        │ ecr:BatchDeleteImage                  │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:BatchGetImage                     │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:CompleteLayerUpload               │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:DeleteRepository                  │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:DescribeImages                    │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:DescribeRepositories              │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:GetDownloadUrlForLayer            │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:InitiateLayerUpload               │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:ListImages                        │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:PutImage                          │                                                       │                                                                                                                       │
│   │                                                                              │        │ ecr:UploadLayerPart                   │                                                       │                                                                                                                       │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/sagemaker/*     │ Allow  │ logs:DescribeLogStreams               │ AWS:${devMermaidSagemakerLauncherRole}                │                                                                                                                       │
│   │ arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/sagemaker/*:*   │        │ logs:FilterLogEvents                  │                                                       │                                                                                                                       │
│   │                                                                              │        │ logs:GetLogEvents                     │                                                       │                                                                                                                       │
│   │                                                                              │        │ logs:StartLiveTail                    │                                                       │                                                                                                                       │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:processing-job/*          │ Allow  │ sagemaker:AddTags                     │ AWS:${devMermaidSagemakerLauncherRole}                │                                                                                                                       │
│   │ arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:training-job/*            │        │ sagemaker:CreateProcessingJob         │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:CreateTrainingJob           │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:DescribeProcessingJob       │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:DescribeTrainingJob         │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:ListTags                    │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:StopProcessingJob           │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:StopTrainingJob             │                                                       │                                                                                                                       │
├───┼──────────────────────────────────────────────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ - │ arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:training-job/*            │ Allow  │ sagemaker:AddTags                     │ AWS:${devMermaidClassifierLauncherRole}               │                                                                                                                       │
│   │                                                                              │        │ sagemaker:CreateTrainingJob           │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:DescribeTrainingJob         │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:ListTags                    │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:ListTrainingJobs            │                                                       │                                                                                                                       │
│   │                                                                              │        │ sagemaker:StopTrainingJob             │                                                       │                                                                                                                       │
└───┴──────────────────────────────────────────────────────────────────────────────┴────────┴───────────────────────────────────────┴───────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[-] AWS::ECR::Repository devMermaidClassifierTrainingRepo devMermaidClassifierTrainingRepoE3510BF2 orphan
[-] AWS::IAM::Role devMermaidClassifierLauncherRole devMermaidClassifierLauncherRole43760877 destroy
[-] AWS::IAM::Policy devMermaidClassifierLauncherRole/DefaultPolicy devMermaidClassifierLauncherRoleDefaultPolicyDC19F8F5 destroy
[-] AWS::IAM::Policy MermaidClassifierLauncherEcrPolicy MermaidClassifierLauncherEcrPolicy9D9D3AFC destroy
[-] AWS::IAM::Policy MermaidClassifierLauncherSagemakerPolicy MermaidClassifierLauncherSagemakerPolicy9C26240B destroy
[-] AWS::IAM::Policy MermaidClassifierLauncherPassRolePolicy MermaidClassifierLauncherPassRolePolicy90E9D5B1 destroy
[+] AWS::ECR::Repository devMermaidClassifierJobsRepo devMermaidClassifierJobsRepoA55861F1
[+] AWS::ECR::Repository devMermaidSegmentationJobsRepo devMermaidSegmentationJobsRepoB7E1ACA2
[+] AWS::IAM::Role devMermaidSagemakerLauncherRole devMermaidSagemakerLauncherRole6C1A5CC5
[+] AWS::IAM::Policy devMermaidSagemakerLauncherRole/DefaultPolicy devMermaidSagemakerLauncherRoleDefaultPolicy7CF2A941
[+] AWS::IAM::Policy MermaidSagemakerLauncherEcrPolicy MermaidSagemakerLauncherEcrPolicy4965385F
[+] AWS::IAM::Policy MermaidSagemakerLauncherSagemakerPolicy MermaidSagemakerLauncherSagemakerPolicy0D9E5DBA
[+] AWS::IAM::Policy MermaidSagemakerLauncherLogsPolicy MermaidSagemakerLauncherLogsPolicy7D7B9929
[+] AWS::IAM::Policy MermaidSagemakerLauncherPassRolePolicy MermaidSagemakerLauncherPassRolePolicy5A2CDAA1

Outputs
[-] Output devMermaidClassifierTrainingRepoUri: {"Description":"ECR URI for the mermaid-classifier training image","Value":{"Fn::Join":["",[{"Fn::Select":[4,{"Fn::Split":[":",{"Fn::GetAtt":["devMermaidClassifierTrainingRepoE3510BF2","Arn"]}]}]},".dkr.ecr.",{"Fn::Select":[3,{"Fn::Split":[":",{"Fn::GetAtt":["devMermaidClassifierTrainingRepoE3510BF2","Arn"]}]}]},".",{"Ref":"AWS::URLSuffix"},"/",{"Ref":"devMermaidClassifierTrainingRepoE3510BF2"}]]},"Export":{"Name":"dev-MermaidClassifierTrainingRepoUri"}}
[-] Output devMermaidClassifierLauncherRoleArn: {"Description":"IAM role for operators driving the mermaid-classifier launcher","Value":{"Fn::GetAtt":["devMermaidClassifierLauncherRole43760877","Arn"]},"Export":{"Name":"dev-MermaidClassifierLauncherRoleArn"}}
[+] Output devMermaidClassifierJobsRepoUri devMermaidClassifierJobsRepoUri: {"Description":"ECR URI for the mermaid-classifier jobs image","Value":{"Fn::Join":["",[{"Fn::Select":[4,{"Fn::Split":[":",{"Fn::GetAtt":["devMermaidClassifierJobsRepoA55861F1","Arn"]}]}]},".dkr.ecr.",{"Fn::Select":[3,{"Fn::Split":[":",{"Fn::GetAtt":["devMermaidClassifierJobsRepoA55861F1","Arn"]}]}]},".",{"Ref":"AWS::URLSuffix"},"/",{"Ref":"devMermaidClassifierJobsRepoA55861F1"}]]},"Export":{"Name":"dev-MermaidClassifierJobsRepoUri"}}
[+] Output devMermaidSegmentationJobsRepoUri devMermaidSegmentationJobsRepoUri: {"Description":"ECR URI for the mermaid-segmentation jobs image","Value":{"Fn::Join":["",[{"Fn::Select":[4,{"Fn::Split":[":",{"Fn::GetAtt":["devMermaidSegmentationJobsRepoB7E1ACA2","Arn"]}]}]},".dkr.ecr.",{"Fn::Select":[3,{"Fn::Split":[":",{"Fn::GetAtt":["devMermaidSegmentationJobsRepoB7E1ACA2","Arn"]}]}]},".",{"Ref":"AWS::URLSuffix"},"/",{"Ref":"devMermaidSegmentationJobsRepoB7E1ACA2"}]]},"Export":{"Name":"dev-MermaidSegmentationJobsRepoUri"}}
[+] Output devMermaidSagemakerLauncherRoleArn devMermaidSagemakerLauncherRoleArn: {"Description":"IAM role for operators driving the mermaid-* launchers","Value":{"Fn::GetAtt":["devMermaidSagemakerLauncherRole6C1A5CC5","Arn"]},"Export":{"Name":"dev-MermaidSagemakerLauncherRoleArn"}}


start: Building prod-mermaid-static-site Template
success: Built prod-mermaid-static-site Template
start: Publishing prod-mermaid-static-site Template (554812291621-us-east-1-b2d960b0)
success: Published prod-mermaid-static-site Template (554812291621-us-east-1-b2d960b0)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)

Stack prod-mermaid-static-site
There were no differences

start: Building prod-mermaid-api-django Template
success: Built prod-mermaid-api-django Template
start: Publishing prod-mermaid-api-django Template (554812291621-us-east-1-9a6e9421)
success: Published prod-mermaid-api-django Template (554812291621-us-east-1-9a6e9421)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)

Stack prod-mermaid-api-django
Resources
[~] AWS::ECS::TaskDefinition ScheduledBackupTaskDef ScheduledBackupTaskDef48789D5A replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -147,7 +147,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:38b11ecd7a34356ccac91b8c61f892e5a4b598bfeed13f480a5dfde2538bcce5"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",
[~] AWS::ECS::TaskDefinition SummaryCacheTaskDef SummaryCacheTaskDefFAAC683D replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -151,7 +151,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:38b11ecd7a34356ccac91b8c61f892e5a4b598bfeed13f480a5dfde2538bcce5"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",
[~] AWS::ECS::TaskDefinition ApiTaskDefinition ApiTaskDefinition51EA709E replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -145,7 +145,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:38b11ecd7a34356ccac91b8c61f892e5a4b598bfeed13f480a5dfde2538bcce5"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",
[~] AWS::ECS::TaskDefinition General/Worker/QueueProcessingTaskDef GeneralWorkerQueueProcessingTaskDef1C2A1522 replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -158,7 +158,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:38b11ecd7a34356ccac91b8c61f892e5a4b598bfeed13f480a5dfde2538bcce5"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",
[~] AWS::ECS::TaskDefinition ImageProcess/Worker/QueueProcessingTaskDef ImageProcessWorkerQueueProcessingTaskDefACA5B138 replace
 └─ [~] ContainerDefinitions (requires replacement)
     └─ @@ -158,7 +158,7 @@
        [ ] ],
        [ ] "Essential": true,
        [ ] "Image": {
        [-]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:38b11ecd7a34356ccac91b8c61f892e5a4b598bfeed13f480a5dfde2538bcce5"
        [+]   "Fn::Sub": "554812291621.dkr.ecr.us-east-1.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-554812291621-us-east-1:f1b10fb84d10387c55d9444ada1d68c488bb91e7e2e51d6eeebd3d5999267ca8"
        [ ] },
        [ ] "LogConfiguration": {
        [ ]   "LogDriver": "awslogs",



✨  Number of stacks with differences: 3


Workflow: pr

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
iac/stacks/sagemaker.py (1)

393-406: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove ecr:DeleteRepository from the shared launcher role (least privilege)

iac/stacks/sagemaker.py includes "ecr:DeleteRepository" in the shared ECR permissions; no other code or docs reference ECR repository deletion in this repo. Granting it lets any launcher operator delete the shared mermaid-*-jobs repositories and break subsequent launches. Remove ecr:DeleteRepository while keeping image-level permissions.

Minimal fix
                         actions=[
                             "ecr:BatchCheckLayerAvailability",
                             "ecr:BatchGetImage",
                             "ecr:GetDownloadUrlForLayer",
                             "ecr:DescribeImages",
                             "ecr:DescribeRepositories",
                             "ecr:ListImages",
                             "ecr:InitiateLayerUpload",
                             "ecr:UploadLayerPart",
                             "ecr:CompleteLayerUpload",
                             "ecr:PutImage",
                             "ecr:BatchDeleteImage",
-                            "ecr:DeleteRepository",
                         ],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iac/stacks/sagemaker.py` around lines 393 - 406, The IAM policy for the
shared launcher role in iac/stacks/sagemaker.py currently includes the
overly-broad action "ecr:DeleteRepository"; remove "ecr:DeleteRepository" from
the actions list in the ECR permissions (the actions=[... ] block used to build
the shared launcher role policy) so the role retains image-level permissions
like PutImage/BatchGetImage but cannot delete repositories. Ensure only
repository-deleting actions are removed and leave all other ecr:* image-layer
actions intact.
🧹 Nitpick comments (1)
iac/nag_suppressions.py (1)

556-575: ⚡ Quick win

Pin these IAM5 suppressions to the current findings.

Without applies_to, any future wildcard added inside these existing launcher policies will be silently blessed by the same suppression. Please scope each suppression to the exact current IAM5 entries so cdk-nag keeps catching later policy growth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iac/nag_suppressions.py` around lines 556 - 575, The NagPackSuppression calls
created via _suppress_by_path for the policy paths (e.g.,
"MermaidSagemakerLauncherEcrPolicy/Resource",
"MermaidSagemakerLauncherSagemakerPolicy/Resource",
"MermaidSagemakerLauncherLogsPolicy/Resource",
"MermaidSagemakerLauncherPassRolePolicy/Resource", and
f"{prefix}MermaidSagemakerLauncherRole/DefaultPolicy/Resource") must include an
applies_to list scoped to the exact current IAM5 findings; update each
NagPackSuppression instantiation in this loop (the one constructed inside
_suppress_by_path) to add an applies_to argument that contains the specific
statement IDs or JSON paths that correspond to the current wildcard entries (so
CDK-Nag will still surface any new wildcard additions), keeping the existing id
("AwsSolutions-IAM5") and reason (using ACCEPTED) otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@iac/stacks/sagemaker.py`:
- Around line 393-406: The IAM policy for the shared launcher role in
iac/stacks/sagemaker.py currently includes the overly-broad action
"ecr:DeleteRepository"; remove "ecr:DeleteRepository" from the actions list in
the ECR permissions (the actions=[... ] block used to build the shared launcher
role policy) so the role retains image-level permissions like
PutImage/BatchGetImage but cannot delete repositories. Ensure only
repository-deleting actions are removed and leave all other ecr:* image-layer
actions intact.

---

Nitpick comments:
In `@iac/nag_suppressions.py`:
- Around line 556-575: The NagPackSuppression calls created via
_suppress_by_path for the policy paths (e.g.,
"MermaidSagemakerLauncherEcrPolicy/Resource",
"MermaidSagemakerLauncherSagemakerPolicy/Resource",
"MermaidSagemakerLauncherLogsPolicy/Resource",
"MermaidSagemakerLauncherPassRolePolicy/Resource", and
f"{prefix}MermaidSagemakerLauncherRole/DefaultPolicy/Resource") must include an
applies_to list scoped to the exact current IAM5 findings; update each
NagPackSuppression instantiation in this loop (the one constructed inside
_suppress_by_path) to add an applies_to argument that contains the specific
statement IDs or JSON paths that correspond to the current wildcard entries (so
CDK-Nag will still surface any new wildcard additions), keeping the existing id
("AwsSolutions-IAM5") and reason (using ACCEPTED) otherwise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ffe8a61c-9422-421a-8e3a-e27cafd7a193

📥 Commits

Reviewing files that changed from the base of the PR and between c2a0520 and 59e64f2.

📒 Files selected for processing (3)
  • iac/nag_suppressions.py
  • iac/sagemaker-launcher-convention.md
  • iac/stacks/sagemaker.py

@gnieuwenhuis gnieuwenhuis changed the title Sagemaker launcher iac Generalize SageMaker launcher IAM roles for classifier and segmentation May 25, 2026
Signed-off-by: Greg Nieuwenhuis <26285069+gnieuwenhuis@users.noreply.github.com>
Copy link
Copy Markdown
Collaborator

@michaelconnor00 michaelconnor00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Added some comments for reference.

Comment thread iac/stacks/sagemaker.py
self,
f"{self.prefix}MermaidClassifierTrainingRepo",
repository_name="mermaid-classifier-training",
f"{self.prefix}MermaidClassifierJobsRepo",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the id and the name will cause a delete and re-create. However, this should just abandon the old one and the removal_policy is RETAIN.

Comment thread iac/stacks/sagemaker.py
role = iam.Role(
self,
f"{self.prefix}MermaidClassifierLauncherRole",
f"{self.prefix}MermaidSagemakerLauncherRole",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause a delete and re-create. Typically with IAM it is not a big deal. Pointing it out incase this resources is referenced anywhere.

@gnieuwenhuis gnieuwenhuis merged commit c4aaf50 into dev May 25, 2026
4 checks passed
@gnieuwenhuis gnieuwenhuis deleted the sagemaker-launcher-iac branch May 25, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants