Attach IAM policy to the IAM user based on Account Condition using CloudFormation

0

Hi AWS, I am trying to deploy a CloudFormation stack to create an IAM user and attach IAM policy to it based on account number. I have used two accounts i.e. Account1 and Account2. The three templates for policy1, policy2 and IAM user are provided below:

IAM POLICY1:

AWSTemplateFormatVersion: 2010-09-09
Description: >
  This template deploys AWS IAM policy to provide s3 access along with KMS
Parameters:
  ReadOnlyBucketARN:
    Type: String
    Description: ARN of the buckets to grant read permissions
  s3WriteBucketAccess:
    Type: String
    Description: ARN of the buckets to grant write permissions
  KMSKeyArn:
    Type: String
    Description: Comma delimited list of KMS Key Arn(s)
  FuncUsername:
    Type: String
    Description: Name for Functional user   
  
Conditions:
  S3WriteBucketAccessProvided: !Not [!Equals [!Ref s3WriteBucketAccess, ""]]
  S3ReadBucketAccessProvided: !Not [!Equals [!Ref ReadOnlyBucketARN, ""]]
  KMSKeysProvided: !Not [!Equals [!Ref KMSKeyArn, ""]]

Resources:
  AccessPolicy1:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub ${FuncUsername}_access_policy1
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - !If
            - S3ReadBucketAccessProvided
            - Sid: "S3ReadAccess"
              Effect: "Allow"
              Action:
                - "s3:List*"
                - "s3:Get*"
              Resource: !Split
                - ","
                - !Ref ReadOnlyBucketARN
            - !Ref "AWS::NoValue"
          - !If
            - S3WriteBucketAccessProvided
            - Sid: "S3WriteAccess"
              Effect: "Allow"
              Action:
                - "s3:PutAnalyticsConfiguration"
                - "s3:AbortMultipartUpload"
                - "s3:PutBucketVersioning"
                - "s3:PutLifecycleConfiguration"
                - "s3:PutInventoryConfiguration"
                - "s3:DeleteObjectVersion"
                - "s3:RestoreObject"
                - "s3:DeleteObject"
                - "s3:DeleteObjectTagging"
                - "s3:PutObjectVersionTagging"
                - "s3:DeleteObjectVersionTagging"
                - "s3:PutObject*"
                - "s3:PutBucketNotification"
              Resource: !Split
                - ","
                - !Ref s3WriteBucketAccess
            - !Ref "AWS::NoValue"
          - !If
            -  KMSKeysProvided
            - Sid: "KMSKeysAccess"
              Effect: "Allow"
              Action:
                - "kms:Decrypt"
                - "kms:Encrypt"
                - "kms:DescribeKey"
                - "kms:ReEncrypt*"
                - "kms:GenerateDataKey*"
                - "kms:RevokeGrant"
                - "kms:ListGrants"
                - "kms:CreateGrant"
              Resource: !Split
                - ","
                - !Ref KMSKeyArn
            - !Ref "AWS::NoValue"
          - Effect: "Allow"
            Action:
              - s3:ListAllMyBuckets
              - s3:HeadBucket
            Resource: "*"
            Condition:
              Bool:
                aws:SecureTransport:
                  - True
          - Effect: "Allow"
            Action:
              - "kms:ListAliases"
            Resource: "*"
            Condition:
              Bool:
                aws:SecureTransport:
                  - True  

Outputs:
  AccessPolicyArn:
    Value: !Ref AccessPolicy1

IAM POLICY2:

#version: 1.0
AWSTemplateFormatVersion: 2010-09-09
Description: >
  This template deploys an IAM policy for a functional user

Parameters:
  FuncUsername:
    Type: String
    Description: Name for Functional user   
  
Resources:
  AccessPolicy2:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub ${FuncUsername}_access_policy2
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: IAMAccess
            Effect: Allow
            Action:
              - iam:*
            Resource: "*"

Outputs:
  AccessPolicy2Arn:
    Value: !Ref AccessPolicy2
          

IAM USER:

# version: 1.0
AWSTemplateFormatVersion: 2010-09-09
Description: >
  This Template Deploys Basic AWS Functional User along with s3 bucket read/write access
Parameters:
  StackNameTag:
    Type: String
    Description: Name of stack as entered above
  TemplateUsedTag:
    Type: String
    Description: Template used in creating this stack
  FuncUsername:
    Type: String
    Description: Name for Functional user   
  s3ReadBucketArn:
    Type: String
    Description: Comma delimited list of s3 bucket Arn for read access
  s3WriteBucketArn:
    Type: String
    Description: Comma delimited list of s3 bucket Arn for read/write access
  kmskeyArn:
    Type: String
    Description: Comma delimited list of kms key Arn
  PrimaryOwner:
    Type: String
    Description: Primary Owner for this user
  SecondaryOwner:
    Type: String
    Description: Secondary Owner for this user
  CostCentre:
    Type: String
    Description: Cost Centre
  BusinessUnit:
    Type: String
    Description: Business Unit
  Account1:
    Type: String
    Description: AWS Account1
  Account2: 
    Type: String
    Description: AWS Account2

Conditions:
  OnlyInAccount1: !Equals 
    - !Ref Account1
    - !Ref 'AWS::AccountId'
  OnlyInAccount2: !Equals 
    - !Ref Account2
    - !Ref 'AWS::AccountId'
  #OnlyInAccount1: !Not [!Equals [!Ref Account1, ""]]
  #OnlyInAccount2: !Not [!Equals [!Ref Account2, ""]]
  # Condition1and2: 
  #   Fn::And:
  #   - Condition: OnlyInAccount1
  #   - Condition: OnlyInAccount2
    

Resources:
  FuncUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref FuncUsername
      ManagedPolicyArns:
        - Fn::GetAtt:
          - FuncUserPolicy
          - Outputs.AccessPolicyArn
        - Fn::GetAtt:
          - FuncUserPolicy2
          - Outputs.AccessPolicy2Arn
      Tags:
        - Key: primary_owner
          Value: !Ref PrimaryOwner
        - Key: secondary_owner
          Value: !Ref SecondaryOwner
        - Key: cost_centre
          Value: !Ref CostCentre
        - Key: business_unit
          Value: !Ref BusinessUnit
        - Key: Creation_Stack
          Value: !Ref StackNameTag
        - Key: Stack_Template
          Value: !Ref TemplateUsedTag

  FuncUserPolicy:
    Type: AWS::CloudFormation::Stack
    Condition: OnlyInAccount1
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Properties:
      TemplateURL: https://aws-billing-report-csv-format-report.s3.amazonaws.com/create-iam-policy1.yaml
      Parameters:
        ReadOnlyBucketARN: !Ref s3ReadBucketArn
        s3WriteBucketAccess: !Ref s3WriteBucketArn
        KMSKeyArn: !Ref kmskeyArn 
        FuncUsername: !Ref FuncUsername

  FuncUserPolicy2:
    Type: AWS::CloudFormation::Stack
    Condition: OnlyInAccount2
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Properties:
      TemplateURL: https://aws-billing-report-csv-format-report.s3.amazonaws.com/create-iam-policy2.yaml
      Parameters:
        FuncUsername: !Ref FuncUsername

As you can see in IAM USER template, the conditions which are commented when I was trying to deploy the stack using the above Conditions both the policy1 and policy2 are getting attached to the IAM user. But when I tried to modify the condition to attach a specific policy either policy1 or policy2 to the user in the given account it is giving me the following error: Template format error: Unresolved resource dependencies [FuncUserPolicy2] in the Resources block of the template

Can someone help me in fixing this issue?

Thanks

2 Answers
1

In the IAM User template you have created resource FuncUser , in this resource you are referencing both FuncUserPolicy and FuncUserPolicy2 via:

ManagedPolicyArns:
        - Fn::GetAtt:
          - FuncUserPolicy
          - Outputs.AccessPolicyArn
        - Fn::GetAtt:
          - FuncUserPolicy2
          - Outputs.AccessPolicy2Arn

If your condition OnlyInAccount2 evaluate to false your resource FuncUserPolicy2 is not created and therefor the reference to it will create the error.

Try and surround your GetAtt with an !IF like you do in AccessPolicy1

profile picture
EXPERT
answered a year ago
  • Hi @JimmyDqv, I have tried using !If condition but getting unresolved dependency error. Mind if you can modify the code using !If condition as suggested by you above.

  • I have not tested but this should hopefully work.

    ManagedPolicyArns:

    • !If
      • OnlyInAccount1
      • !GetAtt [FuncUserPolicy, Outputs.AccessPolicyArn]
      • !Ref "AWS::NoValue"
    • !If
      • OnlyInAccount2
      • !GetAtt [FuncUserPolicy2, Outputs.AccessPolicyArn]
      • !Ref "AWS::NoValue"
0
Accepted Answer

This is the updated IAM user template:

# version: 1.0
AWSTemplateFormatVersion: 2010-09-09
Description: >
  This Template Deploys Basic AWS Functional User along with s3 bucket read/write access
Parameters:
  StackNameTag:
    Type: String
    Description: Name of stack as entered above
  TemplateUsedTag:
    Type: String
    Description: Template used in creating this stack
  FuncUsername:
    Type: String
    Description: Name for Functional user   
  s3ReadBucketArn:
    Type: String
    Description: Comma delimited list of s3 bucket Arn for read access
  s3WriteBucketArn:
    Type: String
    Description: Comma delimited list of s3 bucket Arn for read/write access
  kmskeyArn:
    Type: String
    Description: Comma delimited list of kms key Arn
  PrimaryOwner:
    Type: String
    Description: Primary Owner for this user
  SecondaryOwner:
    Type: String
    Description: Secondary Owner for this user
  CostCentre:
    Type: String
    Description: Cost Centre
  BusinessUnit:
    Type: String
    Description: Business Unit
  Account1:
    Type: String
    Description: AWS Account1
  Account2: 
    Type: String
    Description: AWS Account2

Conditions:
  OnlyInAccount1: !Equals 
    - !Ref Account1
    - !Ref 'AWS::AccountId'
  OnlyInAccount2: !Equals 
    - !Ref Account2
    - !Ref 'AWS::AccountId'
  #OnlyInAccount1: !Not [!Equals [!Ref Account1, ""]]
  #OnlyInAccount2: !Not [!Equals [!Ref Account2, ""]]
  # Condition1and2: 
  #   Fn::And:
  #   - Condition: OnlyInAccount1
  #   - Condition: OnlyInAccount2
    

Resources:
  FuncUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref FuncUsername
      ManagedPolicyArns: 
        - !If
          - OnlyInAccount1
          - !GetAtt [FuncUserPolicy, Outputs.AccessPolicyArn]
          - !Ref "AWS::NoValue"
        - !If
          - OnlyInAccount2
          - !GetAtt [FuncUserPolicy2, Outputs.AccessPolicyArn]
          - !Ref "AWS::NoValue"
        
      Tags:
        - Key: primary_owner
          Value: !Ref PrimaryOwner
        - Key: secondary_owner
          Value: !Ref SecondaryOwner
        - Key: cost_centre
          Value: !Ref CostCentre
        - Key: business_unit
          Value: !Ref BusinessUnit
        - Key: Creation_Stack
          Value: !Ref StackNameTag
        - Key: Stack_Template
          Value: !Ref TemplateUsedTag

  FuncUserPolicy:
    Type: AWS::CloudFormation::Stack
    Condition: OnlyInAccount1
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Properties:
      TemplateURL: https://aws-billing-report-csv-format-report.s3.amazonaws.com/create-iam-policy1.yaml
      Parameters:
        ReadOnlyBucketARN: !Ref s3ReadBucketArn
        s3WriteBucketAccess: !Ref s3WriteBucketArn
        KMSKeyArn: !Ref kmskeyArn 
        FuncUsername: !Ref FuncUsername

  FuncUserPolicy2:
    Type: AWS::CloudFormation::Stack
    Condition: OnlyInAccount2
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Properties:
      TemplateURL: https://aws-billing-report-csv-format-report.s3.amazonaws.com/create-iam-policy2.yaml
      Parameters:
        FuncUsername: !Ref FuncUsername

It worked for me. Thanks @JimmyDqv. However I have one another question if we have to deploy this template as a StackSet targeting two accounts where I need to attach policy1 to account1 and policy2 to account2, then the ManagedPolicyArns condition should be modified to:

ManagedPolicyArns: 
        - !If
          - OnlyInAccount1
          - !GetAtt [FuncUserPolicy, Outputs.AccessPolicyArn]
          - !Ref "AWS::NoValue"
        - !If
          - OnlyInAccount2
          - !GetAtt [FuncUserPolicy2, Outputs.AccessPolicy**2**Arn]
          - !Ref "AWS::NoValue"

Please acknowledge.

profile picture
answered a year ago
profile picture
EXPERT
reviewed a month ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions