Monday, October 20, 2025

Sharing Encrypted AMI with KMS | Deep Dive.

Sharing Encrypted AMI with KMS - Deep Dive.

Focus :

  •         Intro,
  •         Updating the KMS Key Policy (Source Account),
  •         Sharing the AMI (Source Account),
  • Configuring Target Account Permissions,
  •         Launching the Instance (Target Account),
  •         Special Case with Auto Scaling Groups, 
  •         Core Concept Recap (what AMI consists of),
  •         The Encryption Relationship,
  •         What Actually Happens When twtech Shares an Encrypted AMI,
  •         Step-1: twtech shares the encrypted AMI (CLI)
  •         Step-2:  twtech Adds a key policy entry for the KMS key (Sample policy),
  •         Step-3: twtech Adds launch permissions on the AMI (CLI),
  •         Understanding KMS Key Sharing Implications,
  •         What Permissions Are Actually Needed (in target or destination account),
  •         Cross-Region AMI Copy with KMS,
  •         Cross-Account Copy (Best Practice),
  •         Common Pitfalls,
  •         Security Design Patterns,
  •         Observability and Auditing,
  •         Quick Checklist for Encrypted AMI Sharing,
  •         Key Takeaways,
  •         Insights.

 Intro:

    • Sharing an encrypted Amazon Machine Image (AMI) across AWS accounts requires sharing both the AMI itself and the specific Customer Managed Key (CMK) used for its encryption. 
    • twtech cannot share AMIs encrypted with AWS-managed keys (like aws/ebs).
Updating the KMS Key Policy (Source Account)
twtech must modify the CMK's key policy in the source account to grant the target account permissions to use it.
    • Permissions needed: The target account (or specific IAM roles/users within it) requires:
      • kms:DescribeKey
      • kms:CreateGrant
      • kms:Decrypt
      • kms:ReEncrypt*.
    • Principal: Use the target account's root ARN (e.g., arn:aws:iam::DESTINATION_ACCOUNT:root) to allow the target account's administrators to delegate these permissions further.
Sharing the AMI (Source Account)
  • Once the key is shared, twtech must provide launch permissions for the AMI to the target account.
    • Navigate to the EC2 Console
    • select the AMI, 
    • choose Modify Image Permissions,
    • Enter the Account ID of the destination account and save.
 Configuring Target Account Permissions
  • twtech IAM user or role in the target account that will launch instances from the shared AMI must have an IAM policy allowing it to use the source account's KMS key.
    • Action Include:
      •  kms:Decrypt 
      • kms:DescribeKey pointing to the source key's ARN.
 Launching the Instance (Target Account)
When launching an instance from the shared AMI in the target account:
    • Find the AMI under Shared with me in the EC2 Launch Instance Wizard.
    • During the launch, twtech can choose to re-encrypt the volumes with a KMS key owned by the target account to maintain independent control over the data.
Special Case with Auto Scaling Groups
    • If twtech shares AMI with an Auto Scaling Group (ASG), it must create a KMS Grant for the ASG's Service-Linked Role
    • This is because the ASG service needs persistent permission to use the key to launch instances on your behalf.

Core idea:

    •  To securely share the AMI across accounts or regions.

1. Core Concept Recap (what AMI consists of):

    • One or more EBS snapshots (backing volumes),
    • Launch permissions (which accounts can use the AMI to launch instances),
    • Block device mappings (metadata).

NB:

    • When twtech encrypts an AMI (typically using aws ec2 create-image from an encrypted EBS volume or with --kms-key-id), the underlying snapshots are encrypted with a KMS key.
    • Sharing an unencrypted AMI is straightforward
    • But when snapshots are encrypted with KMS, there’s an additional layer:
    • The target account must have permission to use the KMS key that encrypted the snapshots, otherwise they can’t decrypt or launch instances from that AMI.

 2. The Encryption Relationship

    • Each EBS snapshot is encrypted with a single KMS key (CMK).
    • The AMI’s encryption state is derived from those snapshots.
    • The AMI metadata itself isn’t encrypted; encryption applies at the snapshot level.

sharing an encrypted AMI:

    • The AMI launch permission must allow the target account.
    • The EBS snapshot(s) must be shared with the same account(s).
    • The KMS key policy must allow those accounts to use the key for decryption (specifically for DescribeKey, ReEncrypt, CreateGrant, Encrypt, Decrypt, GenerateDataKey*).

 3. What Actually Happens When twtech Shares an Encrypted AMI

    •  Source Account: 111111111111
    •  Target Account: 222222222222
    •   KMS key ARN: arn:aws:kms:us-east-2:111111111111:key/twtecchkmskey-abcd-1234...
    •  Snapshot ARN: arn:aws:ec2:us-east-1:111111111111:snapshot/twtechsnap-0abc123...

Step-1: twtech shares the encrypted AMI

# Share the underlying snapshots

aws ec2 modify-snapshot-attribute \
    --snapshot-id snap-0abc123... \
    --attribute createVolumePermission \
    --operation-type add \
    --user-ids 222222222222

Step-2:  twtech Adds a key policy entry for the KMS key (Sample policy).

twtech must update the key policy in account 111111111111 to allow account 222222222222 to use the key.

# json
{
  "Sid": "twtech Allows use of the key for shared AMI",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::222222222222:root" },
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey",
    "kms:ReEncrypt*",
    "kms:CreateGrant",
    "kms:GenerateDataKey*"
  ],
  "Resource": "*"
}

NB:

  • This allows the target account to create a volume from the snapshot (via EBS APIs) using that key.

Step-3: twtech Adds launch permissions on the AMI (CLI)

# bash
aws ec2 modify-image-attribute \
    --image-id ami-0abcd1234... \
    --launch-permission "Add=[{UserId=222222222222}]"

NB:

  • Now, the target account (destination) can see and launch from that AMI.
  • Dut they’re still dependent on twtech KMS key.

 4. Understanding KMS Key Sharing Implications

  • When twtech shares a KMS-encrypted resource (snapshot, AMI, etc.), twtech is granting cryptographic operations, not ownership.
What the target account can do:

    • Decrypt the data (only for EBS use cases),
    • Create a copy of the snapshot (and re-encrypt with their own key).

What the target account can NOT do:

    • Directly manage or delete twtech key,
    • Revoke or rotate twtech key,
    • Use it outside of EBS for other operations.

NB:

  • This distinction is critical for security and compliance.

 5. What Permissions Are Actually Needed (target account):

    • kms:Decrypt — to decrypt snapshot data blocks during volume creation.
    •  kms:GenerateDataKey* — for creating data keys to encrypt/decrypt during volume use.
    •  kms:DescribeKey — for the EBS service to validate the key.
    •  kms:CreateGrant — sometimes used internally by EBS.

#Sample minimal safe key policy :

# json
{
  "Sid": "twtech Allow EBS in target account to use key",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::222222222222:root"
  },
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey",
    "kms:ReEncrypt*",
    "kms:CreateGrant",
    "kms:GenerateDataKey*"
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:ViaService": "ec2.us-east-2.amazonaws.com"
    }
  }
}

NB:

  • This limits use to the EC2 service, not arbitrary KMS requests.

 6. Cross-Region AMI Copy with KMS

  • twtech cannot directly share an encrypted AMI across regions.
    Instead:
  • Instead twtech:

    1. Copies the AMI to another region.
2. 
During the copy process, it specifies a destination KMS key (in the target region).

Sample:

aws ec2 copy-image \
    --source-image-id ami-0abcd1234 \
    --source-region us-east-2 \
    --region us-west-1 \
    --name "twtechEncryptedCopy" \
    --kms-key-id arn:aws:kms:us-west-1:111111111111:key/twtechkmskey-abcd-5678

NB:

  • twtech can re-encrypt during copy.
  • This is the most common practice to change ownership or key control between regions.

 7. Cross-Account Copy (Best Practice)

When the target account wants its own control over encryption:

1.     twtech shares the source snapshot and KMS key.

2.     Target account copies the snapshot, specifying its own KMS key.

Example (in target account):

aws ec2 copy-snapshot \
    --source-region us-east-2 \
    --source-snapshot-id snap-0abc123 \
    --kms-key-id arn:aws:kms:us-east-2:222222222222:key/twtechkmskey-efgh-9999

NB:

    • This creates a new, independently encrypted snapshot in the target account using their own key.
    • Now they can register their own AMI from that snapshot.
    • no dependency on twtech key anymore.
    • Recommended for production handoffs or regulated environments.

 8. Common Pitfalls

Issue

Root Cause

Resolution

Target account cannot see snapshot

Snapshot not shared

Run modify-snapshot-attribute for snapshot

Target cannot launch AMI

KMS key not shared

Update KMS key policy

Launch fails with “AccessDeniedException”

kms:Decrypt not allowed

Add KMS permission for target

Target AMI creation fails

Source snapshot in different region

Copy snapshot to same region first

Still can’t decrypt

Forgot kms:ViaService condition or region mismatch

Recheck KMS key policy condition block

Using multi-account org

Service Control Policies (SCPs) blocking KMS use

Adjust SCPs or grant exception

 9. Security Design Patterns

Pattern A Producer–Consumer (Simple Sharing)

  •         One producer account creates golden AMIs.
  •         Several consumer accounts (dev/test/prod) can launch but not re-encrypt.
  •         KMS key is owned and controlled by producer.
  •         AMI and KMS key shared explicitly.

Pros: Simpler setup, central governance.
Cons: Consumers depend on producer’s KMS key; can’t rotate independently.

Pattern B Producer–Consumer (Decoupled Ownership)

  •         Producer shares AMI + KMS key temporarily.
  •         Consumer copies snapshot → re-encrypts with its own key.
  •         Producer then revokes KMS sharing.

Pros: Full isolation, independent key management.
Cons: One-time manual copy per version.

Pattern C Automated Pipeline

  •         CI/CD pipeline builds golden AMI (encrypted).
  •         After build, pipeline shares snapshot + key.
  •         Target accounts automatically copy & re-encrypt using AWS Lambda or EventBridge.
  •         Key access automatically revoked post-copy.

Pros: Secure, automated, scalable across many accounts.
Cons: Requires automation maturity.

 10. Observability and Auditing

Monitor via:

·        CloudTrail:

o   CreateGrant, Decrypt, GenerateDataKey events on your KMS key.

o   ModifyImageAttribute and ModifySnapshotAttribute events.

·        AWS Config:

o   Track cross-account resource sharing.

·        EventBridge + SNS:

o   Notify on AMI or snapshot sharing changes.

Security tip:
Add a KMS key usage alarm that alerts if unexpected principals use the key outside normal AMI workflows.

 11. Quick Checklist for Encrypted AMI Sharing

Step

Description

Enable encryption on source AMI (SSE-KMS)

Use your CMK

Share underlying snapshot(s)

Modify snapshot attributes

Update KMS key policy

Allow target accounts

Add AMI launch permissions

Modify image attributes

Verify in target account

DescribeImages, LaunchInstance test

(Optional) Copy snapshot with target’s KMS key

To decouple ownership

Revoke access if needed

Remove key policy and snapshot sharing

 12. Key Takeaways

  •        AMI sharing ≠ snapshot sharing ≠ KMS sharing — you must do all three.
  •         The KMS key is the real gatekeeper — without it, no decryption or volume creation happens.
  •         For secure long-term sharing:

o   Share only temporarily,

o   Automate snapshot copy and re-encryption in target,

o   Keep key policies scoped via kms:ViaService and aws:PrincipalArn conditions.

twtech insights

Here’s a complete JSON bundle that ties together all the moving parts required to share an encrypted AMI across AWS accounts in a secure and compliant way.

This bundle covers:

1.     KMS key policy (source account)

2.     Snapshot sharing (source account)

3.     AMI launch permission modification (source account)

NB:

It is designed for cross-account sharing (one producer → one consumer).

 Scenario Setup

Role

Value

Description

Source account ID

111111111111

Owner of the encrypted AMI

Target account ID

222222222222

Account that will use or copy the AMI

Region

us-east-2

Must be the same for both snapshot and AMI

AMI ID

ami-0abcd1234ef567890

The encrypted AMI you’re sharing

Snapshot ID

snap-0abc123456789def0

Snapshot backing the AMI

KMS Key ID

arn:aws:kms:us-east-2:111111111111:key/twtechkmskey-1111-2222-3333-444444444444

The CMK used for encryption

1, KMS Key Policy (Source Account)

This key policy allows:

  •         Source account (owner) to administer the key,
  •         Target account (consumer) to use the key only for EC2 operations in the same region.

# json
{
  "Version": "2012-10-17",
  "Id": "key-policy-for-ami-share",
  "Statement": [
    {
      "Sid": "twtech Allow key administration by key owner",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "twtech Allow use of key by EC2 and target account for AMI decryption",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::222222222222:root"
      },
      "Action": [
        "kms:Decrypt",
        "kms:DescribeKey",
        "kms:ReEncrypt*",
        "kms:CreateGrant",
        "kms:GenerateDataKey*"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "ec2.us-east-2.amazonaws.com"
        }
      }
    }
  ]
}

NB:

  •         The kms:ViaService condition restricts the target to using this key only through the EC2 service (no arbitrary KMS calls).
  •         twtech can further tighten this with aws:SourceArn = arn:aws:ec2:us-east-2:111111111111:snapshot/snap-0abc123456789def0 if twtech wants to allow decryption for that specific snapshot only.

2, Snapshot Sharing (Source Account)

Snapshots are shared at the EBS layer using the createVolumePermission attribute.
# Here’s the JSON version of that operation’s effect:

{
  "Operation": "ModifySnapshotAttribute",
  "SnapshotSharing": {
    "TargetAccount": "222222222222",
    "Region": "us-east-2",
    "Action": "add",
    "Attribute": "createVolumePermission",
    "SnapshotId": "snap-0abc123456789def0"
  }
}

# Equivalent AWS CLI command:

aws ec2 modify-snapshot-attribute \
  --snapshot-id snap-0abc123456789def0 \
  --attribute createVolumePermission \
  --operation-type add \
  --user-ids 222222222222

Result:

The target account can now see and copy this snapshot if they have KMS access to the encryption key.

3, AMI Launch Permission (Source Account)

Finally, share the AMI itself  by granting the target account launch permissions. This affects the AMI’s metadata (not encryption or snapshots).

{
  "Operation": "ModifyImageAttribute",
  "AmiSharing": {
    "TargetAccount": "222222222222",
    "Region": "us-east-1",
    "Action": "add",
    "Attribute": "launchPermission",
    "ImageId": "ami-0abcd1234ef567890"
  }
}

NB:

# Equivalent AWS CLI command:

aws ec2 modify-image-attribute \
  --image-id ami-0abcd1234ef567890 \
  --launch-permission "Add=[{UserId=222222222222}]"

Result:

Target account can now launch EC2 instances from this AMI — as long as they can decrypt the snapshots (i.e., KMS permissions work).

4, Optional: Copying Snapshot with Target’s Own KMS Key (Best Practice)

# To decouple from the source’s key, the target account can re-encrypt the snapshot:

{
  "Operation": "CopySnapshot",
  "SourceRegion": "us-east-2",
  "SourceSnapshotId": "snap-0abc123456789def0",
  "DestinationKmsKeyId": "arn:aws:kms:us-east-1:222222222222:key/twteechkmskey-5015-6016-7777-888888888888",
  "DestinationRegion": "us-east-1"
}

# Equivalent AWS CLI command:

aws ec2 copy-snapshot \
  --source-region us-east-2 \
  --source-snapshot-id snap-0abc123456789def0 \
  --kms-key-id arn:aws:kms:us-east-1:222222222222:key/twteechkmskey-5015-6016-7777-888888888888Result

  •         The target account now owns a new, independently encrypted snapshot.
  •         They can create their own AMI from that snapshot.
  •         twtech can safely remove their access to your KMS key and snapshot.

5, Optional Tightened KMS Key Policy (if twtech wants to scope even narrower)

# twtech can limit the target’s permissions to the exact snapshot only, like this:

{
  "Sid": "Allow target account to decrypt specific snapshot only",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::accountID:root"
  },
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey",
    "kms:GenerateDataKey*"
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:ViaService": "ec2.us-east-2.amazonaws.com"
    },
    "ArnLike": {
      "aws:SourceArn": "arn:aws:ec2:us-east-1:111111111111:snapshot/snap-0abc123456789def0"
    }
  }
}

NB:

This ensures the key can’t be used by the target for any other snapshots or operations.

 6, Verification Flow

Step

Command

Expected Outcome

1

aws ec2 describe-snapshots --owner-ids 111111111111

Snapshot visible in source

2

aws ec2 describe-snapshots --owner-ids 222222222222

Snapshot visible (shared)

3

aws ec2 describe-images --owners 111111111111

AMI visible

4

In target account, aws ec2 describe-images --executable-users self

Shared AMI visible

5

Launch EC2 instance from AMI

Succeeds if KMS access correct

6

(Optional) Copy snapshot with target’s KMS key

Creates independent snapshot

 Key Takeaways

·        AMI sharing across accounts requires 3 separate permissions:
1, AMI launch permission
2, Snapshot sharing
3, KMS key sharing

  •      Always test with one snapshot first before sharing full multi-volume AMIs.
  •      The safest production pattern is: share copy re-encrypt revoke.
  •      The kms:ViaService condition is your friend — it locks usage to EC2, reducing exposure.
  •      A Sample production-ready CloudFormation template that automates encrypted AMI creation and cross-account sharing with:
    •     Proper KMS key permissions, 
    •     Snapshot sharing,
    •     AMI launch permissions.

    • This version is clean, auditable, and deployable in the source account (the one that owns the encrypted AMI and KMS key).
    • The target account ID is provided as a parameter so you can reuse this template easily for different consumers.

CloudFormation Template for Encrypted AMI Sharing Stack

AWSTemplateFormatVersion: 2010-09-09
Description: >
  Deploys a secure, repeatable setup for sharing a KMS-encrypted AMI
  (including snapshot and KMS key permissions) with a target AWS account.
Parameters:
  TargetAccountId:
    Type: String
    Description: AWS Account ID of the target (consumer) account.
  AmiId:
    Type: String
    Description: The AMI ID to share (must already exist and be encrypted).
  SnapshotId:
    Type: String
    Description: The Snapshot ID backing the AMI.
  Region:
    Type: String
    Default: us-east-2
    Description: AWS Region of the AMI and snapshot.
  KmsKeyAlias:
    Type: String
    Default: shared-ami-key
    Description: Alias name for the KMS CMK used for encryption.
Resources:
  ##################################################
  # 1, Create or reference a KMS CMK for AMI encryption
  ##################################################
  AmiEncryptionKey:
    Type: AWS::KMS::Key
    Properties:
      Description: KMS CMK used to encrypt the AMI and snapshots for sharing
      EnableKeyRotation: true
      KeyPolicy:
        Version: "2012-10-17"
        Id: key-policy-for-ami-share
        Statement:
          # Admin permissions for the key owner (this account)
          - Sid: AllowKeyAdministration
            Effect: Allow
            Principal:
              AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
            Action: "kms:*"
            Resource: "*"
          # Allow EC2 service + target account to use the key for AMI decryption
          - Sid: twtechAllowTargetAccountUse
            Effect: Allow
            Principal:
              AWS: !Sub arn:aws:iam::${TargetAccountId}:root
            Action:
              - kms:Decrypt
              - kms:DescribeKey
              - kms:ReEncrypt*
              - kms:CreateGrant
              - kms:GenerateDataKey*
            Resource: "*"
            Condition:
              StringEquals:
                kms:ViaService: !Sub ec2.${Region}.amazonaws.com
  AmiEncryptionKeyAlias:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: !Sub alias/${KmsKeyAlias}
      TargetKeyId: !Ref AmiEncryptionKey
  ##################################################
  # 2, Snapshot sharing permission
  ##################################################
  SnapshotSharePermission:
    Type: AWS::EC2::Snapshot
    Properties:
      # CloudFormation doesn’t natively modify snapshot attributes,
      # so this section assumes the snapshot already exists and uses a custom resource.
      # The custom resource calls ModifySnapshotAttribute to share the snapshot.
      # In a real stack, you can embed a Lambda-backed custom resource for this action.
      # Placeholder metadata to document the intended permission:
      Tags:
        - Key: SharedWith
          Value: !Ref TargetAccountId
        - Key: Purpose
          Value: SharedEncryptedAMI
  ##################################################
  # 3, Custom Resource — Share Snapshot & AMI (Lambda)
  ##################################################
  ShareAutomationLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ShareEncryptedAmiRole-${AWS::StackName}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: twtechSharePermissionsPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ec2:ModifySnapshotAttribute
                  - ec2:ModifyImageAttribute
                  - ec2:DescribeImages
                  - ec2:DescribeSnapshots
                Resource: "*"
  ShareAutomationLambda:
    Type: AWS::Lambda::Function
    Properties:
      Description: "Shares encrypted AMI and snapshot with target account"
      Handler: index.handler
      Runtime: python3.12
      Timeout: 60
      Role: !GetAtt ShareAutomationLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          def handler(event, context):
              ec2 = boto3.client('ec2')
              try:
                  target = event['ResourceProperties']['TargetAccountId']
                  ami = event['ResourceProperties']['AmiId']
                  snap = event['ResourceProperties']['SnapshotId']
                  # Share snapshot
                  ec2.modify_snapshot_attribute(
                      SnapshotId=snap,
                      Attribute='createVolumePermission',
                      OperationType='add',
                      UserIds=[target]
                  )
                  # Share AMI
                  ec2.modify_image_attribute(
                      ImageId=ami,
                      LaunchPermission={'Add': [{'UserId': target}]}
                  )
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              except Exception as e:
                  print("Error:", e)
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
  ShareAutomationInvoke:
    Type: Custom::ShareAutomation
    Properties:
      ServiceToken: !GetAtt ShareAutomationLambda.Arn
      TargetAccountId: !Ref TargetAccountId
      AmiId: !Ref AmiId
      SnapshotId: !Ref SnapshotId
Outputs:
  SharedAMI:
    Description: The encrypted AMI that was shared with the target account.
    Value: !Ref AmiId
  SharedSnapshot:
    Description: The snapshot backing the AMI that was shared.
    Value: !Ref SnapshotId
  SharedKmsKey:
    Description: The ARN of the KMS key allowing cross-account AMI use.
    Value: !Ref AmiEncryptionKey

 How the automation process Works

1.     Creates a KMS CMK with a scoped key policy

o   Grants decrypt and re-encrypt permissions to the target account only through EC2.

2.     Attaches an alias for easy key management (alias/shared-ami-key).

3.     Invokes a Lambda-backed custom resource that:

o   Shares the EBS snapshot (modify-snapshot-attribute)

o   Adds AMI launch permissions (modify-image-attribute)

4.     Outputs the key, snapshot, and AMI info for reference or automation chaining.

 Deployment Steps

1. Deploy the Stack in Source Account

aws cloudformation deploy \
  --template-file twtechencrypted-ami-share.yaml \
  --stack-name twtechEncryptedAmiShare \
  --parameter-overrides \
      TargetAccountId=222222222222 \
      AmiId=ami-0abcd1234ef567890 \
      SnapshotId=snap-0abc123456789def0 \
      Region=us-east-2

2. In Target Account

# The target can now see and use the AMI:

aws ec2 describe-images --executable-users self

# Or make a copy of the snapshot using their own KMS key:

aws ec2 copy-snapshot  --source-region us-east-2 \
--source-snapshot-id snap-0abc123456789def0 \
--kms-key-id arn:aws:kms:us-west-1:222222222222:key/twtecehkmskey-efgh-5555-6666-7777-8888

 Best Practices

  •         Rotate the KMS key annually (enabled automatically).
  •         If the target copies and re-encrypts snapshots, revoke their key access afterward:
    •    Remove target principal from the KMS key policy.
    •    Optionally remove snapshot and AMI sharing attributes.
  •         Use stack outputs as inputs to CI/CD pipelines that automate AMI promotion across accounts.
  •        Here’s a smple advanced multi-account CloudFormation version of the encrypted AMI sharing stack.
  •        This version scales the previous template to share an encrypted AMI (and its snapshot + KMS key) with multiple target accounts or an entire AWS Organization. 

    •       It’s clean, production-ready, and built for large environments that promote golden AMIs across Dev / QA / Prod or via AWS Organizations OU sharing.

 CloudFormation Template for Multi-Account Encrypted AMI Sharing

AWSTemplateFormatVersion: 2010-09-09
Description: >
  Secure, repeatable setup for sharing a KMS-encrypted AMI and snapshot
  across multiple AWS accounts or AWS Organizations (Dev, QA, Prod, etc.)
Parameters:
  TargetAccountIds:
    Type: CommaDelimitedList
    Description: >
      List of AWS Account IDs to share AMI and KMS key with (e.g. Dev, QA, Prod).
      Example: 111111111111,222222222222,333333333333
  OrganizationId:
    Type: String
    Default: ""
    Description: >
      (Optional) AWS Organization ID (e.g. o-abcd1234). If set, sharing will
      include all accounts in the organization.
  AmiId:
    Type: String
    Description: Existing encrypted AMI ID to share.
  SnapshotId:
    Type: String
    Description: Snapshot ID backing the AMI.
  Region:
    Type: String
    Default: us-east-2
    Description: AWS Region of the AMI and snapshot.
  KmsKeyAlias:
    Type: String
    Default: shared-ami-key
    Description: Alias name for the KMS CMK used for encryption.
Resources:
  ##################################################
  # 1, KMS CMK for AMI/Snapshot Encryption
  ##################################################
  AmiEncryptionKey:
    Type: AWS::KMS::Key
    Properties:
      Description: twtech Multi-account CMK used for encrypted AMI sharing
      EnableKeyRotation: true
      KeyPolicy:
        Version: "2012-10-17"
        Id: multi-account-key-policy
        Statement:
          # Admin rights for this account
          - Sid: AllowKeyAdministration
            Effect: Allow
            Principal:
              AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
            Action: "kms:*"
            Resource: "*"
          # Allow EC2 service + target accounts to use the key
          - Sid: twtechAllowTargetAccountsUse
            Effect: Allow
            Principal:
              AWS: !Ref TargetAccountIds
            Action:
              - kms:Decrypt
              - kms:DescribeKey
              - kms:ReEncrypt*
              - kms:CreateGrant
              - kms:GenerateDataKey*
            Resource: "*"
            Condition:
              StringEquals:
                kms:ViaService: !Sub ec2.${Region}.amazonaws.com
          # Optionally allow AWS Organization
          - Sid: twtechAllowOrganizationUse
            Effect: Allow
            Principal: "*"
            Action:
              - kms:Decrypt
              - kms:DescribeKey
              - kms:ReEncrypt*
              - kms:CreateGrant
              - kms:GenerateDataKey*
            Resource: "*"
            Condition:
              StringEqualsIfExists:
                aws:PrincipalOrgID: !Ref OrganizationId
                kms:ViaService: !Sub ec2.${Region}.amazonaws.com
  AmiEncryptionKeyAlias:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: !Sub alias/${twtechKmsKey-Alias}
      TargetKeyId: !Ref AmiEncryptionKey
  ##################################################
  # 2, Lambda Role to Automate Sharing
  ##################################################
  ShareAutomationLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub MultiAccountAmiShareRole-${AWS::StackName}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/twtechAWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: twtechSharePermissionsPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ec2:ModifySnapshotAttribute
                  - ec2:ModifyImageAttribute
                  - ec2:DescribeImages
                  - ec2:DescribeSnapshots
                Resource: "*"
  ##################################################
  # 3, Lambda Function to Share AMI/Snapshot
  ##################################################
  ShareAutomationLambda:
    Type: AWS::Lambda::Function
    Properties:
      Description: "twtech Shares encrypted AMI and snapshot with multiple target accounts or org"
      Handler: index.handler
      Runtime: python3.12
      Timeout: 120
      Role: !GetAtt ShareAutomationLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          def handler(event, context):
              ec2 = boto3.client('ec2')
              try:
                  props = event['ResourceProperties']
                  targets = [a.strip() for a in props['TargetAccountIds'].split(',') if a.strip()]
                  ami = props['AmiId']
                  snap = props['SnapshotId']
                  org_id = props.get('OrganizationId', '')
                  if org_id:
                      print(f"Sharing with Organization ID: {org_id}")
                      ec2.modify_snapshot_attribute(
                          SnapshotId=snap,
                          Attribute='createVolumePermission',
                          OperationType='add',
                          UserGroups=['all']
                      )
                      ec2.modify_image_attribute(
                          ImageId=ami,
                          LaunchPermission={'Add': [{'Group': 'all'}]}
                      )
                  # Share explicitly with target accounts
                  for account in targets:
                      print(f"Sharing with Account ID: {account}")
                      ec2.modify_snapshot_attribute(
                          SnapshotId=snap,
                          Attribute='createVolumePermission',
                          OperationType='add',
                          UserIds=[account]
                      )
                      ec2.modify_image_attribute(
                          ImageId=ami,
                          LaunchPermission={'Add': [{'UserId': account}]}
                      )
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Targets': targets})
              except Exception as e:
                  print("Error:", e)
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
  ##################################################
  # 4, Custom Resource to Trigger Sharing
  ##################################################
  ShareAutomationInvoke:
    Type: Custom::MultiAccountAmiShare
    Properties:
      ServiceToken: !GetAtt ShareAutomationLambda.Arn
      TargetAccountIds: !Join [",", !Ref TargetAccountIds]
      OrganizationId: !Ref OrganizationId
      AmiId: !Ref AmiId
      SnapshotId: !Ref SnapshotId
Outputs:
  SharedAMI:
    Description: Encrypted AMI shared across multiple target accounts.
    Value: !Ref AmiId
  SharedSnapshot:
    Description: Snapshot backing the AMI.
    Value: !Ref SnapshotId
  SharedKmsKeyArn:
    Description: ARN of the multi-account KMS key.
    Value: !Ref AmiEncryptionKey
  SharedAccounts:
    Description: Target accounts that received sharing permissions.
    Value: !Join [", ", !Ref TargetAccountIds]

How the deployment process Works (Multi-Account / Org Mode)

 KMS Key

    • Grants decrypt + re-encrypt rights to all target accounts listed in TargetAccountIds.
    •  If OrganizationId is specified, it also enables org-wide use for that Org ID via aws:PrincipalOrgID.

Lambda Automation

    •         Iterates through all target accounts and runs:
      •    ModifySnapshotAttribute to grant EBS snapshot access.
      •    ModifyImageAttribute to add AMI launch permission.
    •         If OrganizationId is set, adds an Org-wide sharing group.

Custom Resource

    •      Automatically runs the Lambda at deployment and re-runs on updates.

 Sample Deployments process

# Share to Three Accounts (Dev, QA, Prod)

aws cloudformation deploy \
  --template-file twtech multi-account-ami-share.yaml \
  --stack-name MultiAccountAmiShare \
  --parameter-overrides \
      TargetAccountIds=111111111111,222222222222,333333333333 \
      AmiId=ami-0abcd1234ef567890 \
      SnapshotId=snap-0abc123456789def0 \
      Region=us-east-2

# Share to Entire AWS Organization

aws cloudformation deploy \
  --template-file twtechmulti-account-ami-share.yaml \
  --stack-name OrgWideAmiShare \
  --parameter-overrides \
      OrganizationId=twtechOrgId-abcd1234 \
      AmiId=ami-0abcd1234ef567890 \
      SnapshotId=snap-0abc123456789def0

 Security & Ops Recommendations

    • Rotate KMS CMKs annually (enabled automatically here).
    • Use Organization sharing for central AMI distribution (less operational overhead).
    •  Revoke permissions after promotion or retirement:
aws ec2 modify-image-attribute --image-id ami-xxxx --launch-permission "Remove=[{UserId=111111111111}]"
    • Integrate with CodePipeline or Jenkins for AMI promotion workflows.
    •  Enable CloudTrail for auditing AMI and KMS key usage across accounts.
NB:
  • Extending the automation so that each target account automatically:
    • copies the source AMI’s snapshots, 
    • re-encrypts them with a target-account KMS key, 
    • and registers a new AMI in that account — 
    • This Gives each environment independent, and fully-owned AMIs.
  • Because fully automated cross-account copying requires some cross-account bootstrap, twtech would love to provide two sample CloudFormation templates:

1.     Target-account bootstrap deploy this in each target account (Dev/QA/Prod). 

  •     It creates a minimal IAM role (AmiCopyRole by default) the source-account orchestration can assume to perform EC2 snapshot-copy + register-image operations inside the target account. 

  •     twtech (or the security team) can review & control this role in each target account.

2.     Source-account orchestrator deploy this in the source (producer) account. It:

      •    Creates the KMS key used for sharing in the source account (optional — twtech may already have one),
      •    Shares the snapshot(s) and AMI metadata with targets,
      •    Assumes the target role in each target account and copies every snapshot referenced by the source AMI into the target account using the target account’s KMS key(s),
      •   Registers a new AMI in the target account using the newly-copied snapshots.

 Read First

    •         The automation supports AMIs with one or many EBS-backed snapshots (it discovers block-device mappings and copies each referenced snapshot).
    •         twtech must deploy the Target-account bootstrap in each target account beforehand (or otherwise ensure a role exists that the source account can assume).
    •         Each target KMS key must allow the target role to use it for kms:Encrypt, kms:GenerateDataKey*, kms:DescribeKey, etc. (Key policy / grants live in the target account.)
    •         This design assumes twtech wants the target accounts to own their copies; after copy completes, twtech can unshare the snapshot/AMI or revoke the KMS access the source granted.
    •         The Lambda code uses assume_role to act inside each target account — no secret sharing required.

Template A: Target-account bootstrap (deploy to each target account)

NB:

    • Deploy in each target account to create an IAM role that allows the source-account orchestrator to assume and perform EC2 actions.
    •  Replace SourceAccountId with the ID of the producer/source account.

AWSTemplateFormatVersion: '2010-09-09'
Description: >
  Bootstrap role in target account that the source account's orchestrator
  will assume to copy snapshots and register AMIs into this account.
Parameters:
  SourceAccountId:
    Type: String
    Description: AWS Account ID of the producer/source account (that will assume this role)
  RoleName:
    Type: String
    Default: AmiCopyRole
    Description: Name of role that will be assumed by source account orchestrator 
Resources:
  AmiCopyRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: twtechRef-RoleName
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub arn:aws:iam::${SourceAccountId}:root
            Action: sts:AssumeRole
            Condition: {}
      Description: Role assumed by producer account to copy snapshots and register AMIs in this target account.
      Policies:
        - PolicyName: twtechAmiCopyPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:CopySnapshot
                  - ec2:RegisterImage
                  - ec2:DescribeSnapshots
                  - ec2:DescribeImages
                  - ec2:CreateTags
                  - ec2:DescribeRegions
                Resource: "*"
              - Effect: Allow
                Action:
                  - kms:Encrypt
                  - kms:GenerateDataKey*
                  - kms:DescribeKey
                  - kms:CreateGrant
                Resource: "*" 
Outputs:
  TargetRoleArn:
    Description: ARN of the role the source orchestrator will assume in this account.
    Value: !Sub arn:aws:iam::${AWS::AccountId}:role/${RoleName}

Security notes for template A (target account)

  •         The role trust is intentionally limited to the source account root (twtech can substitute a narrower principal, e.g., a specific role ARN from the source account).
  •         The KMS actions in the inline policy are permissive (Resource: "*") — they are included so operators can more easily use keys in the target account when following best-practice key policies that grant the role usage. In production you should bind KMS permissions to the specific target KMS key ARNs or require key policy grants instead of IAM-based kms:*prefer updating the target key policy to allow this role. (Key policy changes must be made in the target account.)
  •         Installers should ensure the target KMS key policy explicitly allows the role to use the key (recommended).

Template B — Source-account orchestrator (deploy in the source/producer account)

This is the main stack. It:

    •    Shares the source snapshot(s) and AMI,
    •    Assumes the AmiCopyRole in each target account,
    •    Copies all referenced snapshots into the target account re-encrypting with the target KMS key specified,
    •    Registers a new AMI in the target account with the new snapshots.

Required parameter inputs:

    •         TargetAccountIds — Comma-delimited list of target account IDs (order must match TargetKmsKeyIds and you must have already deployed the target bootstrap in each account).
    •         TargetKmsKeyIds — Comma-delimited list of KMS Key ARNs or IDs in each target account (one entry per target account). These keys must exist in the target accounts and permit the target role to encrypt with them.
    •         TargetRoleName — the name of the role created by the Target bootstrap (defaults to AmiCopyRole).

AWSTemplateFormatVersion: '2010-09-09'
Description: >
  Orchestrator in source account: shares an encrypted AMI/snapshots and
  automatically copies & re-encrypts them into each target account (via assume-role).
Parameters:
  TargetAccountIds:
    Type: CommaDelimitedList
    Description: Comma-separated list of target account IDs (order must match TargetKmsKeyIds)
  TargetKmsKeyIds:
    Type: CommaDelimitedList
    Description: Comma-separated list of target KMS key ARNs or IDs for each target account (order must match TargetAccountIds)
  TargetRoleName:
    Type: String
    Default: AmiCopyRole
    Description: Name of the role in target accounts to assume (must match the bootstrap)
  AmiId:
    Type: String
    Description: Source AMI ID to share and copy
  Region:
    Type: String
    Default: us-east-2
    Description: Region of source AMI and snapshots
  SourceSnapshotId:
    Type: String
    Description: One of the snapshot IDs backing the source AMI (this is used for sharing; full discovery is dynamic)
  LambdaTimeout:
    Type: Number
    Default: 300
    Description: Lambda timeout in seconds (increase for large snapshot copies)
Resources:
  ##################################################
  # 1) Optional: source KMS key (if twtech wants CFN to manage it)
  ##################################################
  SourceAmiKey:
    Type: AWS::KMS::Key
    Properties:
      Description: Optional CMK for the source AMI (managed by CFN); if twtech already has a key, ignore or pass existing key ARN in parameters manually (not wired).
      EnableKeyRotation: true
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
          - Sid: twtechAdminAccess
            Effect: Allow
            Principal:
              AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
            Action: 'kms:*'
            Resource: '*'
  ##################################################
  # 2) Lambda Role for Orchestration (assume-role & share)
  ##################################################
  SourceOrchestratorRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub SourceAmiOrchestratorRole-${AWS::StackName}
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/twtechAWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: twtechSourceOrchestratorPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:ModifySnapshotAttribute
                  - ec2:ModifyImageAttribute
                  - ec2:DescribeImages
                  - ec2:DescribeSnapshots
                  - ec2:DescribeRegions
                Resource: "*"
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource: !Sub arn:aws:iam::*:role/${TargetRoleName}
  ##################################################
  # 3) Lambda that shares + assumes target role to copy & register AMI
  ##################################################
  OrchestratorLambda:
    Type: AWS::Lambda::Function
    Properties:
      Description: >
        Shares encrypted AMI/snapshots and copies & re-encrypts them into multiple target accounts.
      Handler: index.handler
      Runtime: python3.12
      Timeout: !Ref LambdaTimeout
      Role: !GetAtt SourceOrchestratorRole.Arn
      Code:
        ZipFile: |
          import boto3, json, time
          import cfnresponse
          from botocore.exceptions import ClientError
          def assume(target_account, role_name):
              sts = boto3.client('sts')
              role_arn = f"arn:aws:iam::{target_account}:role/{role_name}"
              resp = sts.assume_role(RoleArn=role_arn, RoleSessionName='AmiCopySession')
              creds = resp['Credentials']
              return creds
          def wait_for_snapshot_available(ec2_client, snapshot_id, max_wait_minutes=60):
              wait_seconds = 10
              elapsed = 0
              while True:
                  resp = ec2_client.describe_snapshots(SnapshotIds=[snapshot_id])
                  state = resp['Snapshots'][0]['State']
                  if state == 'completed':
                      return True
                  if state == 'error':
                      raise Exception(f"Snapshot {snapshot_id} in error state")
                  time.sleep(wait_seconds)
                  elapsed += wait_seconds
                  if elapsed > max_wait_minutes*60:
                      raise Exception("Timed out waiting for snapshot to complete")
          def handler(event, context):
              print("Event:", json.dumps(event))
              props = event.get('ResourceProperties', {})
              target_accounts = [a.strip() for a in props.get('TargetAccountIds','').split(',') if a.strip()]
              target_keys = [k.strip() for k in props.get('TargetKmsKeyIds','').split(',') if k.strip()]
              role_name = props.get('TargetRoleName','AmiCopyRole')
              ami_id = props['AmiId']
              region = props.get('Region','us-east-1')
              src_snapshot_hint = props.get('SourceSnapshotId')
              if len(target_accounts) != len(target_keys):
                  cfnresponse.send(event, context, cfnresponse.FAILED, {"Error":"TargetAccountIds and TargetKmsKeyIds length mismatch"})
                  return
              ec2_src = boto3.client('ec2', region_name=region)
              try:
                  # 1) Share snapshot hint and AMI with target accounts (so they can describe/copy)
                  for account in target_accounts:
                      try:
                          ec2_src.modify_snapshot_attribute(
                              SnapshotId=src_snapshot_hint,
                              Attribute='createVolumePermission',
                              OperationType='add',
                              UserIds=[account]
                          )
                      except ClientError as e:
                          print("Snapshot share error (may already be shared):", e)
                      try:
                          ec2_src.modify_image_attribute(
                              ImageId=ami_id,
                              LaunchPermission={'Add':[{'UserId': account}]}
                          )
                      except ClientError as e:
                          print("AMI share error (may already be shared):", e)
                  # 2) Discover the AMI block-device mappings (including all source snapshot IDs)
                  desc = ec2_src.describe_images(ImageIds=[ami_id])['Images'][0]
                  block_mappings = desc.get('BlockDeviceMappings', [])
                  # Map of old_snapshot_id -> new_snapshot_id per target
                  source_snap_ids = []
                  for b in block_mappings:
                      ebs = b.get('Ebs')
                      if ebs and 'SnapshotId' in ebs:
                          source_snap_ids.append(ebs['SnapshotId'])
                  # If snapshot list empty, fallback to provided hint
                  if not source_snap_ids and src_snapshot_hint:
                      source_snap_ids = [src_snapshot_hint]
                  results = []
                  # For each target account, assume role and copy each snapshot, then register AMI
                  for idx, target in enumerate(target_accounts):
                      target_key = target_keys[idx]
                      print(f"Processing target {target} using target KMS key {target_key}")
                      creds = assume(target, role_name)
                      # Create target-account EC2 client using assumed credentials
                      ec2_tgt = boto3.client('ec2',
                                            region_name=region,
                                            aws_access_key_id=creds['AccessKeyId'],
                                            aws_secret_access_key=creds['SecretAccessKey'],
                                            aws_session_token=creds['SessionToken']
                                           )
                      new_snapshot_map = {}
                      # Copy each source snapshot into target account
                      for src_snap in source_snap_ids:
                          print(f"Copying snapshot {src_snap} to target {target}")
                          copy_resp = ec2_tgt.copy_snapshot(
                              SourceRegion=region,
                              SourceSnapshotId=src_snap,
                              Description=f"Copy of {src_snap} from {boto3.client('sts').get_caller_identity()['Account']}",
                              KmsKeyId=target_key
                          )
                          new_snap_id = copy_resp['SnapshotId']
                          print("Started copy job, new snapshot id:", new_snap_id)
                          # Wait for copy to complete
                          wait_for_snapshot_available(ec2_tgt, new_snap_id)
                          new_snapshot_map[src_snap] = new_snap_id
                      # Build block device mapping for register_image call using new snapshot ids
                      new_block_mappings = []
                      for b in block_mappings:
                          device_name = b.get('DeviceName')
                          ebs = b.get('Ebs')
                          if ebs and 'SnapshotId' in ebs:
                              old = ebs['SnapshotId']
                              new = new_snapshot_map.get(old)
                              if not new:
                                  raise Exception(f"Missing new snapshot mapping for {old}")
                              ebs_new = {
                                  'SnapshotId': new,
                                  # copy through volume size / deleteOnTermination if present
                                  'DeleteOnTermination': ebs.get('DeleteOnTermination', True),
                                  'VolumeSize': ebs.get('VolumeSize'),
                                  'VolumeType': ebs.get('VolumeType')
                              }
                              new_block_mappings.append({
                                  'DeviceName': device_name,
                                  'Ebs': {k:v for k,v in ebs_new.items() if v is not None}
                              })
                          else:
                              # Non-EBS mapping (e.g., ephemeral) - copy as-is
                             new_block_mappings.append(b)
                      # Choose a name for the new AMI in target account
                      new_ami_name = f"{desc.get('Name','shared-ami')}-copied-from-{boto3.client('sts').get_caller_identity()['Account']}"
                      reg_resp = ec2_tgt.register_image(
                          Name=new_ami_name,
                          Architecture=desc.get('Architecture'),
                          BlockDeviceMappings=new_block_mappings,
                          RootDeviceName=desc.get('RootDeviceName'),
                          VirtualizationType=desc.get('VirtualizationType'),
                          Description=f"Copy of AMI {ami_id} from account {boto3.client('sts').get_caller_identity()['Account']}"
                      )
                      new_ami_id = reg_resp['ImageId']
                      print(f"Registered new AMI {new_ami_id} in account {target}")
                      results.append({'TargetAccount': target, 'NewAmiId': new_ami_id, 'SnapshotMap': new_snapshot_map})
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Results': results})
              except Exception as e:
                  import traceback
                  print("Error during orchestration:", e)
                  traceback.print_exc()
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
  ##################################################
  # 4) Custom Resource to invoke the orchestrator
  ##################################################
  OrchestratorInvoke:
    Type: Custom::OrchestrateAmiCopy
    Properties:
      ServiceToken: !GetAtt OrchestratorLambda.Arn
      TargetAccountIds: !Join [",", !Ref TargetAccountIds]
      TargetKmsKeyIds: !Join [",", !Ref TargetKmsKeyIds]
      TargetRoleName: !Ref TargetRoleName
      AmiId: !Ref AmiId
      Region: !Ref Region
      SourceSnapshotId: !Ref SourceSnapshotId
Outputs:
  OrchestrationResult:
    Description: Orchestration result (list of new AMIs per target account)
    Value: !GetAtt OrchestratorInvoke

How to use these templates (deployment steps)

Step-1.     Deploy Target bootstrap in each target account (one per account)

In Target Account A (Dev):

aws cloudformation deploy \
  --template-file twtechtarget-bootstrap.yaml \
  --stack-name twtechAmiCopyRoleBootstrap \
  --parameter-overrides SourceAccountId=111111111111

NB:

  •  The TargetRoleArn output — that role will be assumed by the source orchestration.

Step-2.     Ensure KMS keys exist in each target and allow the role

o   Create or identify a KMS key in each target account and update the KMS key policy to allow the AmiCopyRole to use it:

§  Actions: kms:Encrypt, kms:GenerateDataKey*, kms:DescribeKey, kms:CreateGrant (or create an explicit grant).

§  Scope as tightly as possible to the role ARN and kms:ViaService = ec2.<region>.amazonaws.com.

Step-3.     Deploy Orchestrator in the source account

# Sample:

aws cloudformation deploy \
  --template-file twtechsource-orchestrator.yaml \
  --stack-name AmiShareOrchestrator \
  --parameter-overrides \
    TargetAccountIds=222222222222,333333333333 \
    TargetKmsKeyIds=arn:aws:kms:us-east-2:222222222222:key/twtechkmskey-aaaa-...,arn:aws:kms:us-east-2:333333333333:key/twtechkmskey-bbbb-... \
    AmiId=ami-0abcd1234ef567890 \
    SourceSnapshotId=snap-0abc123456789def0 \
    Region=us-east-2

4.     Verify

o   In each target account, describe images: aws ec2 describe-images --owners self and you should see a new AMI registered (named like ...-copied-from-<source-account>).

o   Test launching an instance from the new AMI in each target account.

5.     Post-copy cleanup (optional but recommended)

o   Once targets have successfully copied & re-encrypted their snapshots, you can:

§  Remove the target account from the source KMS key policy.

§  Remove the source snapshot createVolumePermission for that target.

§  Remove AMI launch permission if you no longer want live sharing.

Limitations & caveats

        Permissions must be correct in all accounts:

    •    The Source Orchestrator needs permission to share the snapshot and AMI (it has those).
    •    The TargetRole must be created in each target account and must allow the source account to assume it.
    •    The target KMS key policy must allow that role to encrypt / generate data keys.

        Large snapshots take time

    •      copying snapshots may take many minutes/hours depending on data size and AWS internals. 
    •     Lambda timeout may need to be increased, or twtech should implement an asynchronous Step Function or SNS callback. 
    •     The template’s LambdaTimeout default is 300s — 
    •      increase for large data or convert to Step Functions.

        Edge cases:

    •    AMIs with non-EBS / instance-store volumes are copied only by preserving the mapping — 
    •     instance-store cannot be copied as snapshot.
    •    If the source AMI has snapshots across multiple regions, adjust SourceRegion appropriately per snapshot (this template assumes single-region).

        Security

    •     twtech Reviews all IAM and KMS policies to enforce least privilege.
    •     It may Prefer making KMS key policy changes in the target account (grant the target role explicitly rather than relying on IAM kms:*)




No comments:

Post a Comment

Amazon EventBridge | Overview.

Amazon EventBridge - Overview. Scope: Intro, Core Concepts, Key Benefits, Link to official documentation, What EventBridge  Really  Is (Deep...