3
votes

How can I change the IAM Role for a Lambda function in an CloudFormation template of an AWS CodeStar project?

I have created a AWS CodeStar project (web service, Lambda based, Node.js). By default, AWS CodeStar generates the following CloudFormation:

AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31
- AWS::CodeStar

Parameters:
  ProjectId:
    Type: String
    Description: AWS CodeStar projectID used to associate new resources to team members

Resources:
  HelloWorld:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs4.3
      Role:
        Fn::ImportValue:
          !Join ['-', [!Ref 'ProjectId', !Ref 'AWS::Region', 'LambdaTrustRole']]
      Events:
        GetEvent:
          Type: Api
          Properties:
            Path: /
            Method: get
        PostEvent:
          Type: Api
          Properties:
            Path: /
            Method: post

Now, I would like to replace this role with a role of my own, because I need to add policies for the Lambda function to access other AWS resources. At the same time I have also removed the API Gateway, because I will add a scheduler to trigger the Lambda invocations later:

AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31
- AWS::CodeStar

Parameters:
  ProjectId:
    Type: String
    Description: AWS CodeStar projectID used to associate new resources to team members

Resources:
  HelloWorld:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs4.3
      Role: !Ref HelloWorldLambdaRole

  HelloWorldLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      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

However, when I commit and push these changes, AWS CodePipeline fails to update the CloudFormation template:

CREATE_FAILED AWS::IAM::Role EchoLambdaRole API: iam:CreateRole User: arn:aws:sts::[accountId]:assumed-role/CodeStarWorker-[projectId]-CloudFormation/AWSCloudFormation is not authorized to perform: iam:CreateRole on resource: arn:aws:iam::[accountId]:role/awscodestar-[projectId]-lambda-HelloWorldLambdaRole-ABCDEF123456

Based on this feedback, I conclude that CodeStarWorker-[projectId]-CloudFormation/AWSCloudFormation role is not authorized to create IAM roles. However, this role is hidden from my CloudFormation template and to my understanding it is something that is setup by CodeStar automatically. As an AWS account admin, I could simply just edit the associated policy, but IMHO this is not the way to solve this problem.


Edit:

I have checked IAM configuration in my account. An aws-codestar-service-role has been created and it is associated with the AWSCodeStarServiceRole policy that has the following statement (among other statements, see link for details):

{
  "Sid": "ProjectWorkerRoles",
  "Effect": "Allow",
  "Action": [
    "iam:AttachRolePolicy",
    "iam:CreateRole",
    "iam:DeleteRole",
    "iam:DeleteRolePolicy",
    "iam:DetachRolePolicy",
    "iam:GetRole",
    "iam:PassRole",
    "iam:PutRolePolicy",
    "iam:SetDefaultPolicyVersion",
    "iam:CreatePolicy",
    "iam:DeletePolicy",
    "iam:AddRoleToInstanceProfile",
    "iam:CreateInstanceProfile",
    "iam:DeleteInstanceProfile",
    "iam:RemoveRoleFromInstanceProfile"
  ],
  "Resource": [
    "arn:aws:iam::*:role/CodeStarWorker*",
    "arn:aws:iam::*:policy/CodeStarWorker*",
    "arn:aws:iam::*:instance-profile/awscodestar-*"
  ]
},

There is also the CodeStarWorker-[projectId]-CloudFormation role, that has an inline policy named CodeStarWorkerCloudFormationRolePolicy with the following configuration:

{
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:GetObjectVersion"
            ],
            "Resource": [
                "arn:aws:s3:::aws-chargeodestar-eu-west-1-[accountId]-[projectId]-pipeline",
                "arn:aws:s3:::aws-codestar-eu-west-1-[accountId]-[projectId]-pipeline/*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "codestar:SyncResources",
                "lambda:CreateFunction",
                "lambda:DeleteFunction",
                "lambda:AddPermission",
                "lambda:UpdateFunction",
                "lambda:UpdateFunctionCode",
                "lambda:GetFunctionConfiguration",
                "lambda:UpdateFunctionConfiguration",
                "lambda:RemovePermission",
                "apigateway:*",
                "dynamodb:CreateTable",
                "dynamodb:DeleteTable",
                "dynamodb:DescribeTable",
                "kinesis:CreateStream",
                "kinesis:DeleteStream",
                "kinesis:DescribeStream",
                "sns:CreateTopic",
                "sns:DeleteTopic",
                "sns:ListTopics",
                "sns:GetTopicAttributes",
                "sns:SetTopicAttributes",
                "s3:CreateBucket",
                "s3:DeleteBucket"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "iam:PassRole"
            ],
            "Resource": [
                "arn:aws:iam::[accountId]:role/CodeStarWorker-[projectId]-Lambda"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "cloudformation:CreateChangeSet"
            ],
            "Resource": [
                "arn:aws:cloudformation:eu-west-1:aws:transform/Serverless-2016-10-31",
                "arn:aws:cloudformation:eu-west-1:aws:transform/CodeStar"
            ],
            "Effect": "Allow"
        }
    ]
}

Since I have created the project, the CodeStar_[projectId]_Owner policy has been directly attached to my user.


Edit 2:

Despite my own recommendation, I attempted to update the inlined CodeStarWorkerCloudFormationRolePolicy of the CodeStarWorker-[projectId]-CloudFormation role by adding the following policy statement:

{
    "Action": [
        "iam:AttachRolePolicy",
        "iam:CreateRole",
        "iam:DeleteRole",
        "iam:DetachRolePolicy",
        "iam:GetRole",
        "iam:PassRole"
    ],
    "Resource": [
        "arn:aws:iam::699602212296:role/awscodestar-[projectId]-*"
    ],
    "Effect": "Allow"
}

However, this caused the following error in CloudFormation:

CREATE_FAILED AWS::CodeStar::SyncResources SyncResources123456789012 com.amazon.coral.service.InternalFailure
2

2 Answers

1
votes

The CodeStar Service uses a service role called aws-codestar-service-role, with the following statement. You may want to amend this service role if it allows the dynamic worker roles for the project to inherit the IAM role creation action. Otherwise CodeStar could overwrite your changes.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ProjectStack",
      "Effect": "Allow",
      "Action": [
        "cloudformation:*Stack*",
        "cloudformation:GetTemplate"
      ],
      "Resource": [
        "arn:aws:cloudformation:*:*:stack/awscodestar-*",
        "arn:aws:cloudformation:*:*:stack/awseb-*"
      ]
    },
    {
      "Sid": "ProjectStackTemplate",
      "Effect": "Allow",
      "Action": [
        "cloudformation:GetTemplateSummary",
        "cloudformation:DescribeChangeSet"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ProjectQuickstarts",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::awscodestar-*/*"
      ]
    },
    {
      "Sid": "ProjectS3Buckets",
      "Effect": "Allow",
      "Action": [
        "s3:*"
      ],
      "Resource": [
        "arn:aws:s3:::aws-codestar-*",
        "arn:aws:s3:::aws-codestar-*/*",
        "arn:aws:s3:::elasticbeanstalk-*",
        "arn:aws:s3:::elasticbeanstalk-*/*"
      ]
    },
    {
      "Sid": "ProjectServices",
      "Effect": "Allow",
      "Action": [
        "codestar:*Project",
        "codestar:*Resource*",
        "codestar:List*",
        "codestar:Describe*",
        "codestar:Get*",
        "codestar:AssociateTeamMember",
        "codecommit:*",
        "codepipeline:*",
        "codedeploy:*",
        "codebuild:*",
        "ec2:RunInstances",
        "autoscaling:*",
        "cloudwatch:Put*",
        "ec2:*",
        "elasticbeanstalk:*",
        "elasticloadbalancing:*",
        "iam:ListRoles",
        "logs:*",
        "sns:*"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ProjectWorkerRoles",
      "Effect": "Allow",
      "Action": [
        "iam:AttachRolePolicy",
        "iam:CreateRole",
        "iam:DeleteRole",
        "iam:DeleteRolePolicy",
        "iam:DetachRolePolicy",
        "iam:GetRole",
        "iam:PassRole",
        "iam:PutRolePolicy",
        "iam:SetDefaultPolicyVersion",
        "iam:CreatePolicy",
        "iam:DeletePolicy",
        "iam:AddRoleToInstanceProfile",
        "iam:CreateInstanceProfile",
        "iam:DeleteInstanceProfile",
        "iam:RemoveRoleFromInstanceProfile"
      ],
      "Resource": [
        "arn:aws:iam::*:role/CodeStarWorker*",
        "arn:aws:iam::*:policy/CodeStarWorker*",
        "arn:aws:iam::*:instance-profile/awscodestar-*"
      ]
    },
    {
      "Sid": "ProjectTeamMembers",
      "Effect": "Allow",
      "Action": [
        "iam:AttachUserPolicy",
        "iam:DetachUserPolicy"
      ],
      "Resource": "*",
      "Condition": {
        "ArnEquals": {
          "iam:PolicyArn": [
            "arn:aws:iam::*:policy/CodeStar_*"
          ]
        }
      }
    },
    {
      "Sid": "ProjectRoles",
      "Effect": "Allow",
      "Action": [
        "iam:CreatePolicy",
        "iam:DeletePolicy",
        "iam:CreatePolicyVersion",
        "iam:DeletePolicyVersion",
        "iam:ListEntitiesForPolicy",
        "iam:ListPolicyVersions"
      ],
      "Resource": [
        "arn:aws:iam::*:policy/CodeStar_*"
      ]
    },
    {
      "Sid": "InspectServiceRole",
      "Effect": "Allow",
      "Action": [
        "iam:ListAttachedRolePolicies"
      ],
      "Resource": [
        "arn:aws:iam::*:role/aws-codestar-service-role"
      ]
    }
  ]
}

Also see http://docs.aws.amazon.com/codestar/latest/userguide/access-permissions.html but as you may have guessed, it's relatively new and the docs don't cover your use case.

0
votes

I believe the answer is that CodeStar appears to be inconsistent with the naming convention it uses to create roles in different situations. If when creating the role you provide a name with a prefix of CodeStar-[projectId]* this will satisfy the IAM policy of the CodeStarWorker-[projectId]-CloudFormation role. i.e. include `RoleName: !Sub 'CodeStar-${ProjectId}-....'.

Further information When I encountered the same error I was also going to update the IAM policy and then noticed the permission boundary in the IAM policy of the CodeStarWorker-[projectId]-CloudFormation role. Compare the role in the error to the existing role:

Role in error: awscodestar-[projectId]-lambda-HelloWorldLambdaRole-ABCDEF123456.

Role created by CodeStar example code: CodeStar-[projectId]-Execution

As an additional side note, this is confusing if you've come to CodeStar from using the SAM CLI, as with the CLI you can specify a lambda function without a role and SAM will create it for you, for example:

$ sam init --name test_sam
$ cat test_sam/template.yml
HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
        CodeUri: hello-world/
        Handler: app.lambdaHandler
        Runtime: nodejs8.10
        Environment: 
            Variables:
                PARAM1: VALUE
        Events:
            HelloWorld:
                Type: Api
                Properties:
                    Path: /hello
                    Method: get

However, in CodeStar this wouldn't work and it appears that you need to follow CodeStar's example and specify the function resource, as well as the role with the right name prefix! e.g.

Resources:
  HelloWorld:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: python3.7
      Role:
        Fn::GetAtt:
        - LambdaExecutionRole
        - Arn
      Events:
        GetEvent:
          Type: Api
          Properties:
            Path: /
            Method: get
  LambdaExecutionRole:
    Description: Creating service role in IAM for AWS Lambda
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub 'CodeStar-${ProjectId}-Execution${Stage}'
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service: [lambda.amazonaws.com]
          Action: sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        -  ....