1

I'm working on a template that allows to create a user, tag it, and add it to any groups, add any roles, managed policies and in-line policies if needed:

## =================== VERSION ===================
AWSTemplateFormatVersion: '2010-09-09'

## =================== DESCRIPTION ===================
Description: >-
  Creates an IAM User if none is present, optionally manage to which IAM group(s) the user belongs to
  Optionally embed AWS managed policies, customer managed policies and inline policies in the user

## =================== METADATA ===================
Metadata: 
  AWS::CloudFormation::Interface: 
    ParameterGroups: 
      -
        Label:
          default: Set a one-time password 
        Parameters:
          - paramOneTimePassword
    ParameterLabels:
      paramOneTimePassword:
        default: ' '

## =================== PARAMETERS ===================
Parameters:
  paramOneTimePassword:
    Description: The user will have to pick a new password upon first sign in
    Type: String    
    NoEcho: 'true' # mask the parameter value as asterisks (*****) to prevent it from being displayed in the console, CLI, or API
    ConstraintDescription: Password must be between 8 and 32 characters, start with lowercase or uppercase letter, and can be alphanumeric with the following special characters !@#$%&

## =================== MAPPINGS ===================
Mappings:
  mapUserData:
    UserName:
      AWS: testuser
      Real: 'Test User'
    Groups:
      Group: BackendDev
    Tags:
      product: testProduct
  mapManagedPolicies:
    CustomerManagedPolicies: 
      ARN: None

## =================== CONDITIONS ===================
Conditions:
  hasManagedPolicy:
    !Not [!Equals [!FindInMap [mapManagedPolicies, CustomerManagedPolicies, ARN], 'None'] ]
 
## =================== RESOURCES ===================
Resources:
  myInlinePolicyForChangeOwnPassword:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: IAM-ChangeOwnPassword
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Sid: ViewAccountPasswordRequirements
            Action: 'iam:GetAccountPasswordPolicy'
            Resource: "*"
          - Effect: Allow
            Sid: ChangeOwnPassword
            Action:
              - 'iam:GetUser'
              - 'iam:ChangePassword'
            Resource: !Join ['', [arn:aws:iam::*:user/,!FindInMap [mapUserData, UserName, AWS]]]
      Users:
        - !FindInMap [mapUserData, UserName, AWS]
  myUser:
    Type: 'AWS::IAM::User'
    Properties:
      UserName: !FindInMap [mapUserData, UserName, AWS]
      LoginProfile:
        Password: !Ref paramOneTimePassword 
        PasswordResetRequired: true 
      Groups:
        - !FindInMap [mapUserData, Groups, Group]
      ManagedPolicyArns: # list of ARNs of IAM managed policies that you want to attach to the user
        - !If [ hasManagedPolicy, !FindInMap [mapManagedPolicies, CustomerManagedPolicies, ARN], !Ref "AWS::NoValue" ]
      Tags:
        - Key: name
          Value: !FindInMap [mapUserData, UserName, Real]
        - Key: product
          Value: !FindInMap [mapUserData, Tags, product]
  WaitCondition:
    Type: AWS::CloudFormation::WaitCondition
    DependsOn: myUser
    Properties:
      # Handle: !Ref WaitHandle
      Timeout: 120
      Count: 1

## =================== OUTPUT ===================
Outputs:
  outputArn:
    Description: User ARN
    Value: !GetAtt myUser.Arn
  outputName:
    Description: User name
    Value: !Ref myUser

Thing is that there's a race condition between the moment the user creation is triggered, and the moment CF tries to create/attach the in-line policy to the user: if the user haven't finished creating, the stack deployment will fail.

I read about using WaitCondition but I'm not really sure how to employ it here with IAM resources. I'd greatly appreciate any feedback that can point me into the right direction.

Thanks

1 Answer 1

1

It turns out I was doing it wrong due to me being new to CloudFormation.

For anyone else looking into how to attach both managed and inline policies to a user at the moment of user creation, this is how I do it which incidentally is pretty well documented by the AWS folks:

## =================== VERSION ===================
AWSTemplateFormatVersion: '2010-09-09'

## =================== DESCRIPTION ===================
Description: >-
  Creates an IAM User if none is present, optionally manage to which IAM group(s) the user belongs to
  Optionally embed AWS managed policies, customer managed policies and inline policies in the user

## =================== METADATA ===================
Metadata: 
  AWS::CloudFormation::Interface: 
    ParameterGroups: 
      -
        Label:
          default: Set a one-time password 
        Parameters:
          - paramOneTimePassword
    ParameterLabels:
      paramOneTimePassword:
        default: ' '

## =================== PARAMETERS ===================
Parameters:
  paramOneTimePassword:
    Description: The user will have to pick a new password upon first sign in
    Type: String    
    NoEcho: 'true' # mask the parameter value as asterisks (*****) to prevent it from being displayed in the console, CLI, or API
    ConstraintDescription: Password must be between 8 and 32 characters, start with lowercase or uppercase letter, and can be alphanumeric with the following special characters !@#$%&

## =================== MAPPINGS ===================
Mappings:
  mapUserData:
    UserName:
      AWS: testuser
      Real: 'Test User'
    Groups:
      Group: BackendDev
    Tags:
      product: testProduct
  mapManagedPolicies:
    CustomerManagedPolicies: 
      ARN: None

## =================== CONDITIONS ===================
Conditions:
  hasManagedPolicy:
    !Not [!Equals [!FindInMap [mapManagedPolicies, CustomerManagedPolicies, ARN], 'None'] ]
 
## =================== RESOURCES ===================
Resources:
  # myInlinePolicyForChangeOwnPassword:
  #   Type: 'AWS::IAM::Policy'
  #   Properties:
  myUser:
    Type: 'AWS::IAM::User'
    Properties:
      UserName: !FindInMap [mapUserData, UserName, AWS]
      LoginProfile:
        Password: !Ref paramOneTimePassword 
        PasswordResetRequired: true 
      ManagedPolicyArns: # list of ARNs of IAM managed policies that you want to attach to the user
        - !If [ hasManagedPolicy, !FindInMap [mapManagedPolicies, CustomerManagedPolicies, ARN], !Ref "AWS::NoValue" ]
      Policies:
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Sid: ViewAccountPasswordRequirements
                Action: 'iam:GetAccountPasswordPolicy'
                Resource: "*"
              - Effect: Allow
                Sid: ChangeOwnPassword
                Action:
                  - 'iam:GetUser'
                  - 'iam:ChangePassword'
                Resource: !Join ['', [arn:aws:iam::*:user/,!FindInMap [mapUserData, UserName, AWS]]]
          PolicyName: IAM-ChangeOwnPassword
      Groups:
        - !FindInMap [mapUserData, Groups, Group]
      Tags:
        - Key: name
          Value: !FindInMap [mapUserData, UserName, Real]
        - Key: product
          Value: !FindInMap [mapUserData, Tags, product]

## =================== OUTPUT ===================
Outputs:
  outputArn:
    Description: User ARN
    Value: !GetAtt myUser.Arn
  outputName:
    Description: User name
    Value: !Ref myUser

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.