Skip to content

Grant the DataOps deploy role least-privilege access to the mailing-export bucket policy #118

Closed

Description

@alexeygrigorev

Grant the DataOps deploy role least-privilege access to the mailing-export bucket policy

Status: pending
Tags: bug, infra, P1
Depends on: None
Blocks: #108

Scope

Update the authoritative GitHub Actions OIDC deploy-role template at ../aws-infra/sandbox/dataops/template.github-actions.yaml so CloudFormation can create, read, update, and remove only the AWS::S3::BucketPolicy attached to #108's MailingExportsBucket in the dataops-v1 application stack.

Add one dedicated IAM statement; do not extend the existing broad bucket-administration statement. Its exact permission boundary is:

  • actions: s3:GetBucketPolicy, s3:PutBucketPolicy, and s3:DeleteBucketPolicy only;
  • resource: arn:${AWS::Partition}:s3:::${FullDocsStackName}-mailingexportsbucket-* only (the CloudFormation physical-name pattern for logical resource MailingExportsBucket);
  • no Resource: "*", object ARN (/*), object-data action, access-point ARN, unrelated DataOps bucket, cross-region resource, or additional S3 permission.

Keep source and live infrastructure aligned. After the source change passes review and the normal issue gates, a credentialed AWS operator must apply that exact template to the dataops-github-actions stack in eu-west-1 with CAPABILITY_NAMED_IAM. The operator must review the change set before execution, confirm it changes only the deploy role's inline policy as expected, and reconcile the resulting live role policy and stack template against committed source. Do not hand-edit the IAM role in the console or with put-role-policy.

This issue is an infrastructure prerequisite for deploying #108: #118 can be implemented and source-tested independently, but #108's MailingExportsBucketPolicy cannot be deployed successfully until the live dataops-github-actions stack contains this permission. Do not mark #108 deployment unblocked from a source-only commit.

Acceptance Criteria

  • The authoritative ../aws-infra/sandbox/dataops/template.github-actions.yaml contains a dedicated mailing-export bucket-policy statement with exactly s3:GetBucketPolicy, s3:PutBucketPolicy, and s3:DeleteBucketPolicy on arn:${AWS::Partition}:s3:::${FullDocsStackName}-mailingexportsbucket-*.
  • Automated/static tests prove the statement includes all three required actions and reject Resource: "*", object resources, unrelated bucket patterns, and additional S3 actions in that statement.
  • Existing deploy-role permissions, OIDC trust restrictions, parameters, and unrelated ../aws-infra resources are unchanged; the dirty sandbox/datamailer/ work and other user-owned changes are neither modified nor included.
  • The template passes local CloudFormation lint/static validation and AWS cloudformation validate-template in eu-west-1; validation output contains no secrets or private operational data.
  • [HUMAN] A credentialed AWS operator creates and reviews a change set for dataops-github-actions, verifies the only intended live change is the deploy role inline-policy addition, then executes it with CAPABILITY_NAMED_IAM and confirms the stack reaches UPDATE_COMPLETE (or CREATE_COMPLETE if reconstructed).
  • [HUMAN] Post-apply reconciliation confirms (a) the live stack template matches the committed authoritative source for this statement, (b) the live deploy-role policy has exactly the three scoped bucket-policy actions/resource above, and (c) there was no console/direct-IAM drift.
  • After the live prerequisite is reconciled, Automate private Mailchimp account exports and attach them to recurring work #108's normal GitHub Actions deployment can create/update MailingExportsBucketPolicy without AccessDenied; no real Mailchimp export, archive download, or personal-data handling is required for this check.
  • Tester records exact commands, exit codes, validation/test counts where available, sanitized change-set/stack status, and source/live reconciliation evidence in Grant the DataOps deploy role least-privilege access to the mailing-export bucket policy #118 without posting account IDs, role ARNs, policy IDs, credentials, or other private operational data.

Test Scenarios

Scenario: Exact source-policy boundary

Given: the authoritative deploy-role template
When: the focused policy test inspects the mailing-export statement
Then: only the three bucket-policy actions are allowed, only the generated mailing-export bucket-name pattern is targeted, and object/unrelated/wildcard resources are absent

Scenario: Reviewed live apply

Given: the approved source template and the existing dataops-github-actions stack in eu-west-1
When: a credentialed operator creates and reviews the change set before executing it
Then: only the intended inline-policy permission changes, the stack completes successfully, and source/live reconciliation shows no direct-IAM drift

Scenario: #108 deployment unblock

Given: the reconciled live deploy role and #108's application template containing MailingExportsBucketPolicy
When: the standard main GitHub Actions deployment updates dataops-v1
Then: CloudFormation manages that bucket policy without an S3 bucket-policy authorization failure

Scenario: Failed apply and rollback

Given: validation, change-set review, stack update, or #108 deployment fails
When: the failure is inspected
Then: the operator preserves CloudFormation event evidence, allows/initiates stack rollback as appropriate, does not broaden the IAM resource/action scope as a shortcut, and returns the source/live state to the last known committed/reconciled version before retrying

Required Verification

  • A focused automated test over ../aws-infra/sandbox/dataops/template.github-actions.yaml for exact action and resource equality, including negative assertions for wildcard/object/unrelated S3 access.
  • Local CloudFormation lint/static validation available in ../aws-infra; if no repository-standard runner exists, use a non-mutating parser/linter and document the command.
  • aws cloudformation validate-template --template-body file://template.github-actions.yaml --region eu-west-1 from ../aws-infra/sandbox/dataops/ using the credentialed operator session.
  • A reviewed CloudFormation change set followed by the documented stack apply for dataops-github-actions; record only sanitized summaries.
  • Read-only post-apply checks using cloudformation get-template/describe-stacks and the live role policy, compared structurally with committed source.
  • The normal Automate private Mailchimp account exports and attach them to recurring work #108/DataOps deployment and On-Call monitoring after both issues have passed their required pipeline gates.

Rollback

  • If the dataops-github-actions update fails, inspect sanitized stack events and let CloudFormation roll back to the prior stable role policy; do not patch IAM directly.
  • If the permission must be withdrawn, first remove or roll back Automate private Mailchimp account exports and attach them to recurring work #108's MailingExportsBucketPolicy dependency, then revert the dedicated statement in source and apply the reverted dataops-github-actions template through CloudFormation.
  • Reconcile committed source, the live stack template, and the live role policy after rollback. A source revert without a stack apply is not a completed rollback.

Human / AWS Boundary

  • Software Engineer owns the source-template change and automated tests only. Tester and PM review source and non-mutating validation evidence.
  • Creating/executing the IAM change set, applying dataops-github-actions, and exercising the live deploy role are AWS writes and require the explicitly authorized credentialed operator ([HUMAN]).
  • Read-only AWS inspection may support reconciliation, but no agent should expose account identifiers, credentials, private policy metadata, or Mailchimp/export data in this public issue.
  • ../aws-infra has no self-deploying CI/CD path; committing or pushing the template alone does not update the role.

Out of Scope

  • Implementing, testing, or accepting Automate private Mailchimp account exports and attach them to recurring work #108's Mailchimp connector, API, portal UI, storage behavior, or real export.
  • Adding object read/write permissions, changing Lambda runtime permissions, changing the mailing-export bucket policy itself, or modifying any other S3 bucket's permissions.
  • Refactoring existing broad deploy-role statements or resolving unrelated aws-infra drift.
  • Modifying DataTalksClub/dataops application code, ../dtc-operations, ../datatasks, or ../podcast-assistant.

Activity

  1. added
    needs groomingRaw intake that needs PM grooming
    infraDeployment and infrastructure
    P1Important
    bugSomething is broken
    and removed
    needs groomingRaw intake that needs PM grooming
    on Jul 14, 2026
  2. changed the title [-]Allow DataOps CI to manage the mailing-export bucket policy[/-] [+]Grant the DataOps deploy role least-privilege access to the mailing-export bucket policy[/+] on Jul 14, 2026
  3. alexeygrigorev commented on Jul 14, 2026

    @alexeygrigorev
    MemberAuthor

    GROOMED

    Issue #118 is now agent-ready as the least-privilege infrastructure prerequisite for #108.

    Key decisions:

    The existing dirty sandbox/datamailer/ and other user-owned changes are explicitly outside scope.

  4. alexeygrigorev commented on Jul 14, 2026

    @alexeygrigorev
    MemberAuthor

    Software Engineer handoff — ready for Tester

    Implemented the groomed source-only portion in an isolated aws-infra worktree on branch issue-118-mailing-bucket-policy, based on current origin/main (a457ece). No commit, push, change set, stack update, or direct IAM write was performed.

    Changed files

    • sandbox/dataops/template.github-actions.yaml
      • Added a dedicated MailingExportsBucketPolicy statement.
      • Its only actions are s3:GetBucketPolicy, s3:PutBucketPolicy, and s3:DeleteBucketPolicy.
      • Its only resource is arn:${AWS::Partition}:s3:::${FullDocsStackName}-mailingexportsbucket-*.
    • sandbox/dataops/tests/test_template_github_actions.py
      • Added dependency-free structural tests for the real template.
      • Negative cases cover a missing required action, extra S3 action, duplicate action, wildcard resource, object resource, and unrelated bucket resource.

    Verification

    • python3 -m unittest discover -s sandbox/dataops/tests -v — PASS, 7 tests.
    • sam validate --lint --template-file sandbox/dataops/template.github-actions.yaml — PASS; valid SAM/CloudFormation template.
    • aws cloudformation validate-template --template-body file://template.github-actions.yaml --region eu-west-1 --query 'Description' --output text from sandbox/dataops/ — PASS; returned the expected sanitized template description.
    • git diff --check — PASS.
    • Final status contains only the two scoped source/test paths above. The original dirty aws-infra checkout, including its sandbox/datamailer/ and template changes, was not modified.

    Acceptance status

    • Source statement exactness: PASS.
    • Positive/negative automated policy-boundary tests: PASS.
    • Local and AWS template validation: PASS.
    • Existing trust policy, parameters, and unrelated resources unchanged in this branch: PASS by focused diff.
    • Reviewed CloudFormation change set/apply, live reconciliation, and Automate private Mailchimp account exports and attach them to recurring work #108 deployment unblock: PENDING [HUMAN] and intentionally not attempted before Tester/PM acceptance.
  5. alexeygrigorev commented on Jul 14, 2026

    @alexeygrigorev
    MemberAuthor

    TEST PASS — source/non-mutating Tester gate

    Independent review verified the implementation in the isolated aws-infra-118 worktree. No file was edited, committed, pushed, deployed, or applied during testing.

    Commands and results

    • python3 -m unittest discover -s sandbox/dataops/tests -v — exit 0; 7/7 passed.
      • Positive exact-boundary assertion passed.
      • Negative mutations were rejected for: missing required action, added S3 action, duplicate action, wildcard resource, object resource, and unrelated bucket resource.
    • sam validate --lint --template-file sandbox/dataops/template.github-actions.yaml — exit 0; valid SAM/CloudFormation template.
    • From sandbox/dataops/: aws cloudformation validate-template --template-body file://template.github-actions.yaml --region eu-west-1 --query 'Description' --output text — exit 0; returned the expected sanitized template description.
    • git diff --check — exit 0.
    • Scoped status/privacy scan — exit 0:
      • 1 tracked path and 1 untracked path only: the authoritative DataOps deploy template and its focused test.
      • 0 out-of-scope paths.
      • 0 sandbox/datamailer/ paths.
      • 0 credential/private-key/credentialed-URL signature hits.
    • Read-only AWS reconciliation commands (describe-stacks, get-template, and get-role-policy, with structural-only output) — exit 0:
      • stack status: UPDATE_COMPLETE;
      • live stack template matching statement count: 0;
      • live role-policy matching statement count: 0.

    Note: an initial validate-template invocation from repository root used the issue-relative filename and exited nonzero because that file is relative to sandbox/dataops/. The required command was rerun from the specified directory and passed as recorded above.

    Acceptance criteria

    1. PASS — one dedicated MailingExportsBucketPolicy statement has exactly s3:GetBucketPolicy, s3:PutBucketPolicy, and s3:DeleteBucketPolicy, with Effect: Allow, on only arn:${AWS::Partition}:s3:::${FullDocsStackName}-mailingexportsbucket-*.
    2. PASS — focused automated/static coverage proves exact equality and the requested least-privilege negative cases. Exact resource equality also excludes access-point and cross-region resources.
    3. PASS — the focused diff adds only that statement and its test; trust policy, parameters, existing permissions, unrelated resources, and dirty sandbox/datamailer/ work are absent from the diff.
    4. PASS — local SAM lint and AWS template validation pass; posted outputs are sanitized.
    5. PENDING [HUMAN] — reviewed CloudFormation change set and stack apply were intentionally not performed by Tester.
    6. PENDING [HUMAN] — read-only inspection confirms the live statement is still absent, so source/live reconciliation is not yet complete.
    7. PENDING after human apply — Automate private Mailchimp account exports and attach them to recurring work #108 must remain blocked until the live prerequisite is applied/reconciled and its normal deployment proves bucket-policy management without AccessDenied.
    8. PASS for Tester evidence — exact source-validation commands, counts, scoped diff scan, sanitized stack status, and structural live reconciliation are recorded here without private identifiers or operational data.

    No UI changed, so screenshots are not applicable.

    The implementation is approved for PM review and source commit. This verdict does not approve or claim the human AWS apply, live reconciliation, or #108 deployment unblock.

  6. alexeygrigorev commented on Jul 14, 2026

    @alexeygrigorev
    MemberAuthor

    ACCEPTED — agent-verifiable source/non-mutating scope

    PM final acceptance passes for the implementation that agents can safely verify.

    • The isolated issue-118-mailing-bucket-policy worktree is based on origin/main at a457ece and contains only the authoritative template modification plus its focused test file.
    • The dedicated MailingExportsBucketPolicy statement has Effect: Allow, exactly s3:GetBucketPolicy, s3:PutBucketPolicy, and s3:DeleteBucketPolicy, and exactly arn:${AWS::Partition}:s3:::${FullDocsStackName}-mailingexportsbucket-*.
    • The resource is bucket-only. There is no wildcard resource, object suffix, access-point/cross-region form, unrelated bucket pattern, or additional S3 action.
    • Existing trust restrictions, parameters, permissions, and unrelated resources are untouched by the focused diff. The original dirty sandbox/datamailer/ checkout is outside this worktree and outside the candidate change.
    • Independent PM verification passed: 7/7 focused unit tests, SAM/CloudFormation lint, AWS validate-template in eu-west-1, and git diff --check. No UI changed, so screenshots are not applicable.
    • Independent read-only reconciliation reports the live dataops-github-actions stack as UPDATE_COMPLETE, with 0 matching statements in both its live template and deploy-role policy. This is the expected pre-apply state, not live acceptance.

    The Software Engineer may now make the focused source commit. Because human criteria remain, the commit must reference rather than close #118, the human label must be added, and the issue must remain open.

    Still pending [HUMAN]:

    1. A credentialed operator must create and review a CloudFormation change set for dataops-github-actions, verify only the intended inline-policy addition, and execute it with CAPABILITY_NAMED_IAM.
    2. The operator must reconcile committed source, the live stack template, and the live role policy, confirming exactly this statement and no direct-IAM drift.
    3. Only after that reconciliation may Automate private Mailchimp account exports and attach them to recurring work #108 be considered infrastructure-unblocked; its normal GitHub Actions deployment must then demonstrate that CloudFormation can manage MailingExportsBucketPolicy without AccessDenied.

    No AWS write, commit, push, or deployment was performed during PM acceptance.

  7. added
    humanCode done or issue blocked on human verification
    on Jul 14, 2026
  8. alexeygrigorev commented on Jul 14, 2026

    @alexeygrigorev
    MemberAuthor

    Human AWS gate remaining — issue stays open

    Accepted source commit: 7f69ed581654284b11660fd0edcb30c54182373d in DataTalksClub/aws-infra (not yet pushed at the time of this comment). The source change is complete, but #118 must remain open until the committed template is applied and reconciled by a credentialed operator.

    Run these steps only after the accepted commit has been pushed to aws-infra main, from a clean checkout of that exact commit in sandbox/dataops/. Do not use iam put-role-policy, the AWS console policy editor, or any direct IAM write.

    Change-set review and apply

    1. Confirm git status --short is empty and git rev-parse HEAD is the accepted commit on the shared main history.
    2. Re-run source checks:
      • python3 -m unittest discover -s tests -v
      • sam validate --lint --template-file template.github-actions.yaml
      • aws cloudformation validate-template --template-body file://template.github-actions.yaml --region eu-west-1
    3. Choose a unique change-set name and create an UPDATE change set for dataops-github-actions with --template-body file://template.github-actions.yaml, --capabilities CAPABILITY_NAMED_IAM, --region eu-west-1, and UsePreviousValue=true for all existing parameters: GitHubOwner, GitHubRepo, GitHubBranch, DeployRoleName, FullDocsStackName, FullDocsRegion, and SamArtifactBucketName.
    4. Wait with aws cloudformation wait change-set-create-complete --stack-name dataops-github-actions --change-set-name <name> --region eu-west-1.
    5. Review aws cloudformation describe-change-set --stack-name dataops-github-actions --change-set-name <name> --region eu-west-1 before execution. The only intended resource change is an in-place Modify of GitHubActionsDeployRole for the inline-policy addition; there must be no replacement, parameter change, trust-policy change, unrelated resource change, or additional IAM/S3 permission. Record only a sanitized summary in this public issue.
    6. If the review is exact, execute with aws cloudformation execute-change-set --stack-name dataops-github-actions --change-set-name <name> --region eu-west-1, then wait with aws cloudformation wait stack-update-complete --stack-name dataops-github-actions --region eu-west-1.
    7. Confirm aws cloudformation describe-stacks --stack-name dataops-github-actions --region eu-west-1 --query 'Stacks[0].StackStatus' --output text returns UPDATE_COMPLETE. Do not post account IDs, role ARNs, policy IDs, or raw policy documents.

    Source/live reconciliation

    1. Retrieve the live stack template read-only with aws cloudformation get-template --stack-name dataops-github-actions --region eu-west-1 and compare the MailingExportsBucketPolicy statement structurally with committed template.github-actions.yaml.
    2. Resolve the deployed role name from the stack/parameters without posting it, then read the inline policy with aws iam get-role-policy --role-name <role-name> --policy-name dataops-v1-deploy.
    3. Confirm the live statement has exactly:
      • actions: s3:GetBucketPolicy, s3:PutBucketPolicy, s3:DeleteBucketPolicy;
      • resource: arn:${AWS::Partition}:s3:::${FullDocsStackName}-mailingexportsbucket-* after CloudFormation substitution;
      • no object ARN, wildcard resource, unrelated bucket pattern, additional S3 action, or trust-policy change.
    4. Confirm the live stack template, live inline policy, and committed source agree and that no direct-IAM/console drift exists. Post only the sanitized result and stack status.
    5. Only after reconciliation, allow Automate private Mailchimp account exports and attach them to recurring work #108's normal main GitHub Actions deployment and verify CloudFormation can manage MailingExportsBucketPolicy without AccessDenied.

    Rollback checklist

    • If validation or change-set review is wrong, do not execute; delete the change set with CloudFormation and correct the source through the normal issue gates.
    • If execution fails, preserve sanitized describe-stack-events evidence and allow/wait for CloudFormation rollback to the prior stable state; do not broaden permissions or patch IAM directly.
    • If the stack reaches UPDATE_ROLLBACK_COMPLETE, reconcile the live template and role policy with the last known committed/applied version before retrying.
    • If the permission must be withdrawn after a successful apply, first remove or roll back Automate private Mailchimp account exports and attach them to recurring work #108's dependent MailingExportsBucketPolicy, revert this dedicated statement in aws-infra source, review a new CloudFormation UPDATE change set, and apply the revert through CloudFormation with CAPABILITY_NAMED_IAM.
    • After any rollback, confirm stable stack status and reconcile committed source, the live stack template, and the live role policy. A source revert alone is not a completed rollback.
  9. alexeygrigorev commented on Jul 14, 2026

    @alexeygrigorev
    MemberAuthor

    ON-CALL / CREDENTIALED AWS APPLY — PASS

    The accepted aws-infra source commit 7f69ed581654284b11660fd0edcb30c54182373d is on DataTalksClub/aws-infra main and was applied through CloudFormation. No direct IAM or console edit was used.

    Preflight and source validation (all exit 0):

    • Clean isolated worktree at the exact accepted commit; local HEAD and origin/main both matched.
    • python3 -m unittest discover -s tests -v — 7/7 passed.
    • sam validate --lint --template-file template.github-actions.yaml — valid.
    • aws cloudformation validate-template --template-body file://template.github-actions.yaml --region eu-west-1 — valid; sanitized description matched.
    • git diff --check — passed.
    • Pre-apply stack drift detection — DETECTION_COMPLETE, IN_SYNC.

    Reviewed change set:

    • Status CREATE_COMPLETE; execution status AVAILABLE before execution.
    • All seven existing parameters used their previous values and the declared capability was CAPABILITY_NAMED_IAM.
    • Exactly one resource change: in-place Modify of GitHubActionsDeployRole (AWS::IAM::Role), Replacement=False, property scope only.
    • The sole detailed target was Policies, static/direct modification, RequiresRecreation=Never.
    • The change-set candidate template structurally matched committed source exactly. The pre-apply live template structurally matched committed source with only MailingExportsBucketPolicy absent.
    • There were no replacements, deletions, trust-policy changes, parameter changes, unrelated resource changes, or additional permissions. The change set was therefore executed.

    Apply and post-apply reconciliation:

    • CloudFormation completed successfully; stack status is UPDATE_COMPLETE and no failed stack events were present.
    • The live stack template structurally matches committed source at 7f69ed5.
    • The live inline policy contains exactly one MailingExportsBucketPolicy statement.
    • That live statement has exactly s3:GetBucketPolicy, s3:PutBucketPolicy, and s3:DeleteBucketPolicy; its resolved resource is exactly the expected mailing-export bucket-name pattern, is bucket-only, and has no object suffix, unrelated bucket, wildcard resource, or extra action.
    • The OIDC trust policy retained its expected single-statement/action/condition shape.
    • Post-apply drift detection completed IN_SYNC, confirming no direct-IAM/console drift.

    The two [HUMAN] AWS criteria are satisfied and #108 is now infrastructure-unblocked. #108 must still pass its own normal main GitHub Actions deployment and confirm MailingExportsBucketPolicy is managed without AccessDenied; this report does not claim that separate application deployment or a real Mailchimp export occurred.

    ON-CALL DONE: source/live reconciled; prerequisite applied successfully

  10. removed
    humanCode done or issue blocked on human verification
    on Jul 14, 2026
  11. alexeygrigorev commented on Jul 14, 2026

    @alexeygrigorev
    MemberAuthor

    Closed after the reviewed CloudFormation apply and source/live reconciliation completed successfully. #108 is infrastructure-unblocked; its application deployment and real-provider human verification remain tracked there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1ImportantbugSomething is brokeninfraDeployment and infrastructure

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions