Repository: serverless/serverless (JavaScript)
Date: 2016-11-01 13:46:25+00:00
Task type: Feature

Issue:
Feature Request: Per Function Custom IAM roles
##### Feature Request: Per Function Custom IAM Roles

At present, custom roles can only be defined at the service level (#1749), which I don't believe is granular enough. Individual functions should allow for individual roles.
##### Benefits:
- Improved security
##### Drawbacks:
- Complexity, however, users could ignore this and choose the per service approach



Issue discussion:
Hey @danhumphrey thanks for this one! Good idea.

I would propose to implement the same fallback we're about to implement with the `package` per function functionality (see here: https://github.com/serverless/serverless/issues/1777). So you can basically implement it on a per-service level or per-function level.

What do you think about this @flomotlik @eahefnawy 

Hi @pmuens thanks for the quick response.

I think the fallback option is perfect - function role is used if defined, otherwise, service role is applied. :)

Looping in @ac360 who responded to my question about this on Gitter.

Hello @danhumphrey 
We are using this Approach in sls 0.5.

We generate for each function a own policy with [Troposphere](https://github.com/cloudtools/troposphere) 

The problem currently is that we have to generate the resource template before we can deploy the serverless resources.
It would be awesome if serverless would support this out of the box.

Yup I like this feature. I've also added the help-wanted label. I'm not sure how fast we're going to get around to implementing this, but we'd love to have it in if somebody is able to provide a PR for it

I would add that those roles should be also part of the serverless cloudformation stack - that is you should be able to reference the custom resources you define, which you don't know the ARN beforehand. You can then deploy the service across AWS accounts for e.g. without any hardcoding of ARNs.


Solution patch:
diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md
index f7de8396a77..0d7e732ca93 100644
--- a/docs/providers/aws/guide/functions.md
+++ b/docs/providers/aws/guide/functions.md
@@ -160,7 +160,7 @@ functions:
     memorySize: 512
 ```
 
-You can also use an existing IAM role by adding your IAM Role ARN in the `iamRoleARN` property. For example:
+You can also use an existing IAM role by adding your IAM Role ARN in the `role` property. For example:
 
 ```yml
 # serverless.yml
@@ -168,10 +168,10 @@ You can also use an existing IAM role by adding your IAM Role ARN in the `iamRol
 service: new-service
 provider:
   name: aws
-  iamRoleARN: arn:aws:iam::YourAccountNumber:role/YourIamRole
+  role: arn:aws:iam::YourAccountNumber:role/YourIamRole
 ```
 
-Support for separate IAM Roles per function is coming soon.
+See the documentation about [IAM](./iam.md) for function level IAM roles.
 
 ## VPC Configuration
 
diff --git a/docs/providers/aws/guide/iam.md b/docs/providers/aws/guide/iam.md
new file mode 100644
index 00000000000..89c7d46338b
--- /dev/null
+++ b/docs/providers/aws/guide/iam.md
@@ -0,0 +1,289 @@
+<!--
+title: Serverless Framework - AWS Lambda Guide - IAM
+menuText: IAM
+menuOrder: 12
+description: How to set up the different roles on a service and function level
+layout: Doc
+-->
+
+<!-- DOCS-SITE-LINK:START automatically generated  -->
+### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/iam)
+<!-- DOCS-SITE-LINK:END -->
+
+# Defining IAM Rights
+
+Serverless provides no-configuration rights provisioning by default.
+However you can always define roles on a service or function level if you need to.
+
+## Default Role Management
+
+The default rights provisioning approach requires no configuration and defines a role that is shared by all of the Lambda functions in your service. A policy is also created and is attached to the generated role. Any additional specific rights are added to the role by defining provider level `iamRoleStatements` that will be merged into the generated policy.
+
+### Adding Custom IAM Role Statements to the Default Policy
+
+By default, your Lambda functions will be provided with the right to create and write to CloudWatch logs. Further, if you have specified VPC security groups and subnets for your lambdas to use then the EC2 rights necessary to attach to the VPC via an ENI will be added into the default IAM policy.
+
+If you want to give permission to your functions to access certain resources on your AWS account, you can add custom IAM role statements to your service by adding the statements in the `iamRoleStatements` array in the `provider` object. As those statements will be merged into the CloudFormation template you can use `Join`, `Ref` or any other CloudFormation method or feature. You're also able to either use YAML for defining the statement (including the methods) or use embedded JSON if you prefer it. Here's an example that uses all of these:
+
+```yml
+service: new-service
+
+provider:
+  name: aws
+  iamRoleStatements:
+    -  Effect: 'Allow'
+       Action:
+         - 's3:ListBucket'
+       Resource:
+         Fn::Join:
+           - ''
+           - - 'arn:aws:s3:::'
+           - Ref: ServerlessDeploymentBucket
+    -  Effect: "Allow"
+       Action:
+         - "s3:PutObject"
+       Resource:
+         Fn::Join:
+           - ''
+           - - 'arn:aws:s3:::'
+             - Ref: ServerlessDeploymentBucket
+```
+
+On deployment, all these statements will be added to the policy that is applied to the IAM role that is assumed by your Lambda functions.
+
+## Custom Role Management
+
+**WARNING:** You need to take care of the overall role setup as soon as you define custom roles.
+That means that `iamRoleStatements` you've defined on the `provider` level won't be applied anymore. Furthermore you need to provide the corresponding permissions for your Lambdas `logs` and [`stream`](../events/streams.md) events.
+
+Serverless empowers you to define custom roles and apply them to your functions on a provider or individual function basis. To do this you must declare a `role` attribute at the level at which you would like the role to be applied.
+
+Defining it on the provider will make the role referenced by the `role` value the default role for any Lambda without its own `role` declared. This is to say that defining a `role` attribute on individual functions will override any provider level declared role. If every function within your service has a role assigned to it (either via provider level `role` declaration, individual declarations, or a mix of the two) then the default role and policy will not be generated and added to your Cloud Formation Template.
+
+The `role` attribute can have a value of the logical name of the role, the ARN of the role, or an object that will resolve in the ARN of the role. The declaration `{ function: { role: 'myRole' } }` will result in `{ 'Fn::GetAtt': ['myRole', 'Arn'] }`. You can of course just declare an ARN like so `{ function: { role: 'an:aws:arn:xxx:*:*' } }`. This use case is primarily for those who must create their roles and / or policies via a means outside of Serverless.
+
+Here are some examples of using these capabilities to specify Lambda roles.
+
+### Provide a single role for all lambdas (via each form of declaration)
+
+```yml
+service: new-service
+
+provider:
+  name: aws
+  # declare one of the following...
+  role: myDefaultRole                                                  # must validly reference a role defined in the service
+  role: arn:aws:iam::0123456789:role//my/default/path/roleInMyAccount  # must validly reference a role defined in your account
+  role:                                                                # must validly resolve to the ARN of a role you have the rights to use
+    Fn::GetAtt:
+      - myRole
+      - Arn
+
+functions:
+  func0: # will assume 'myDefaultRole'
+    ... # does not define role
+  func1: # will assume 'myDefaultRole'
+    ... # does not define role
+
+resources:
+  Resources:
+    myDefaultRole:
+      Type: AWS::IAM::Role
+      Properties:
+        Path: /my/default/path
+        RoleName: MyDefaultRole
+        AssumeRolePolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+            - Effect: Allow
+              Principal:
+                Service:
+                  - lambda.amazonaws.com
+              Action: sts:AssumeRole
+        Policies:
+          - PolicyName: myPolicyName
+            PolicyDocument:
+              Version: '2012-10-17'
+              Statement:
+                - Effect: Allow # note that these rights are given in the default policy and are required if you want logs out of your lambda(s)
+                  Action:
+                    - logs:CreateLogGroup
+                    - logs:CreateLogStream
+                    - logs:PutLogEvents
+                  Resource: arn:aws:logs:${region}:${accountId}:log-group:/aws/lambda/*:*:*
+                -  Effect: "Allow"
+                   Action:
+                     - "s3:PutObject"
+                   Resource:
+                     Fn::Join:
+                       - ""
+                       - - "arn:aws:s3:::"
+                         - "Ref" : "ServerlessDeploymentBucket"
+```
+
+### Provide individual roles for each Lambda
+
+```yml
+service: new-service
+
+provider:
+  name: aws
+  ... # does not define role
+
+functions:
+  func0:
+    role: myCustRole0
+    ...
+  func1:
+    role: myCustRole1
+    ...
+
+resources:
+  Resources:
+    myCustRole0:
+      Type: AWS::IAM::Role
+      Properties:
+        Path: /my/cust/path
+        RoleName: MyCustRole0
+        AssumeRolePolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+            - Effect: Allow
+              Principal:
+                Service:
+                  - lambda.amazonaws.com
+              Action: sts:AssumeRole
+        Policies:
+          - PolicyName: myPolicyName
+            PolicyDocument:
+              Version: '2012-10-17'
+              Statement:
+                - Effect: Allow
+                  Action:
+                    - logs:CreateLogGroup
+                    - logs:CreateLogStream
+                    - logs:PutLogEvents
+                  Resource: arn:aws:logs:${region}:${accountId}:log-group:/aws/lambda/*:*:*
+                - Effect: Allow
+                  Action:
+                    - ec2:CreateNetworkInterface
+                    - ec2:DescribeNetworkInterfaces
+                    - ec2:DetachNetworkInterface
+                    - ec2:DeleteNetworkInterface
+                  Resource: "*"
+    myCustRole1:
+      Type: AWS::IAM::Role
+      Properties:
+        Path: /my/cust/path
+        RoleName: MyCustRole1
+        AssumeRolePolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+            - Effect: Allow
+              Principal:
+                Service:
+                  - lambda.amazonaws.com
+              Action: sts:AssumeRole
+        Policies:
+          - PolicyName: myPolicyName
+            PolicyDocument:
+              Version: '2012-10-17'
+              Statement:
+                - Effect: Allow # note that these rights are given in the default policy and are required if you want logs out of your lambda(s)
+                  Action:
+                    - logs:CreateLogGroup
+                    - logs:CreateLogStream
+                    - logs:PutLogEvents
+                  Resource: arn:aws:logs:${region}:${accountId}:log-group:/aws/lambda/*:*:*
+                -  Effect: "Allow"
+                   Action:
+                     - "s3:PutObject"
+                   Resource:
+                     Fn::Join:
+                       - ""
+                       - - "arn:aws:s3:::"
+                         - "Ref" : "ServerlessDeploymentBucket"
+```
+
+### Provide a default role for all Lambdas except those overriding the default
+
+```yml
+service: new-service
+
+provider:
+  name: aws
+  role: myDefaultRole
+
+functions:
+  func0:
+    role: myCustRole0
+    ...
+  func1:
+    ... # does not define role
+
+resources:
+  Resources:
+    myDefaultRole:
+      Type: AWS::IAM::Role
+      Properties:
+        Path: /my/default/path
+        RoleName: MyDefaultRole
+        AssumeRolePolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+            - Effect: Allow
+              Principal:
+                Service:
+                  - lambda.amazonaws.com
+              Action: sts:AssumeRole
+        Policies:
+          - PolicyName: myPolicyName
+            PolicyDocument:
+              Version: '2012-10-17'
+              Statement:
+                - Effect: Allow # note that these rights are given in the default policy and are required if you want logs out of your lambda(s)
+                  Action:
+                    - logs:CreateLogGroup
+                    - logs:CreateLogStream
+                    - logs:PutLogEvents
+                  Resource: arn:aws:logs:${region}:${accountId}:log-group:/aws/lambda/*:*:*
+                -  Effect: "Allow"
+                   Action:
+                     - "s3:PutObject"
+                   Resource:
+                     Fn::Join:
+                       - ""
+                       - - "arn:aws:s3:::"
+                         - "Ref" : "ServerlessDeploymentBucket"
+    myCustRole0:
+      Type: AWS::IAM::Role
+      Properties:
+        Path: /my/cust/path
+        RoleName: MyCustRole0
+        AssumeRolePolicyDocument:
+          Version: '2012-10-17'
+          Statement:
+            - Effect: Allow
+              Principal:
+                Service:
+                  - lambda.amazonaws.com
+              Action: sts:AssumeRole
+        Policies:
+          - PolicyName: myPolicyName
+            PolicyDocument:
+              Version: '2012-10-17'
+              Statement:
+                - Effect: Allow
+                  Action:
+                    - logs:CreateLogGroup
+                    - logs:CreateLogStream
+                    - logs:PutLogEvents
+                  Resource: arn:aws:logs:${region}:${accountId}:log-group:/aws/lambda/*:*:*
+                - Effect: Allow
+                  Action:
+                    - ec2:CreateNetworkInterface
+                    - ec2:DescribeNetworkInterfaces
+                    - ec2:DetachNetworkInterface
+                    - ec2:DeleteNetworkInterface
+                  Resource: "*"
+```
diff --git a/docs/providers/aws/guide/plugins.md b/docs/providers/aws/guide/plugins.md
index 3fc12e52899..a190a744600 100644
--- a/docs/providers/aws/guide/plugins.md
+++ b/docs/providers/aws/guide/plugins.md
@@ -1,7 +1,7 @@
 <!--
 title: Serverless Framework - AWS Lambda Guide - Plugins
 menuText: Plugins
-menuOrder: 12
+menuOrder: 13
 description: How to install and create Plugins to extend or overwrite the functionality of the Serverless Framework
 layout: Doc
 -->
diff --git a/docs/providers/aws/guide/workflow.md b/docs/providers/aws/guide/workflow.md
index a73ff04c54a..714b2db8ab2 100644
--- a/docs/providers/aws/guide/workflow.md
+++ b/docs/providers/aws/guide/workflow.md
@@ -1,7 +1,7 @@
 <!--
 title: Serverless Framework Guide - AWS Lambda - Workflow
 menuText: Workflow
-menuOrder: 13
+menuOrder: 14
 description: A guide and cheatsheet containing CLI commands and workflow recommendations.
 layout: Doc
 -->
@@ -67,4 +67,4 @@ serverless invoke function -f [FUNCTION NAME] -s [STAGE NAME] -r [REGION NAME] -
 Open up a separate tab in your console and stream all logs for a specific Function using this command.
 ```
 serverless logs -f [FUNCTION NAME] -s [STAGE NAME] -r [REGION NAME]
-```
\ No newline at end of file
+```
diff --git a/lib/plugins/aws/deploy/compile/events/stream/index.js b/lib/plugins/aws/deploy/compile/events/stream/index.js
index 07284e90f90..bbbb278a5e1 100644
--- a/lib/plugins/aws/deploy/compile/events/stream/index.js
+++ b/lib/plugins/aws/deploy/compile/events/stream/index.js
@@ -113,20 +113,23 @@ class AwsCompileStreamEvents {
               };
             }
 
-            // update the PolicyDocument statements
-            const statement = this.serverless.service.provider.compiledCloudFormationTemplate
-              .Resources
-              .IamPolicyLambdaExecution
-              .Properties
-              .PolicyDocument
-              .Statement;
-
-            this.serverless.service.provider.compiledCloudFormationTemplate
-              .Resources
-              .IamPolicyLambdaExecution
-              .Properties
-              .PolicyDocument
-              .Statement = statement.concat([streamStatement]);
+            // update the PolicyDocument statements (if default policy is used)
+            if (this.serverless.service.provider.compiledCloudFormationTemplate
+              .Resources.IamPolicyLambdaExecution) {
+              const statement = this.serverless.service.provider.compiledCloudFormationTemplate
+                .Resources
+                .IamPolicyLambdaExecution
+                .Properties
+                .PolicyDocument
+                .Statement;
+
+              this.serverless.service.provider.compiledCloudFormationTemplate
+                .Resources
+                .IamPolicyLambdaExecution
+                .Properties
+                .PolicyDocument
+                .Statement = statement.concat([streamStatement]);
+            }
 
             const newStreamObject = {
               [`${normalizedFunctionName}EventSourceMapping${
diff --git a/lib/plugins/aws/deploy/compile/functions/index.js b/lib/plugins/aws/deploy/compile/functions/index.js
index c88cdfb2acb..6a15b40b669 100644
--- a/lib/plugins/aws/deploy/compile/functions/index.js
+++ b/lib/plugins/aws/deploy/compile/functions/index.js
@@ -17,6 +17,17 @@ class AwsCompileFunctions {
     };
   }
 
+  compileRole(role) {
+    if (typeof role === 'object') {
+      // role is an "Fn::GetAtt" object
+      return role;
+    } else if (role.indexOf(':') === -1) {
+      // role is a Logical Role Name
+      return { 'Fn::GetAtt': [role, 'Arn'] };
+    }
+    return role; // indicates that role is a Role ARN
+  }
+
   compileFunction(functionName) {
     const newFunction = this.cfLambdaFunctionTemplate();
     const functionObject = this.serverless.service.getFunction(functionName);
@@ -70,10 +81,12 @@ class AwsCompileFunctions {
       newFunction.Properties.Description = functionObject.description;
     }
 
-    if (typeof this.serverless.service.provider.iamRoleARN === 'string') {
-      newFunction.Properties.Role = this.serverless.service.provider.iamRoleARN;
+    if ('role' in functionObject) {
+      newFunction.Properties.Role = this.compileRole(functionObject.role);
+    } else if ('role' in this.serverless.service.provider) {
+      newFunction.Properties.Role = this.compileRole(this.serverless.service.provider.role);
     } else {
-      newFunction.Properties.Role = { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] };
+      newFunction.Properties.Role = this.compileRole('IamRoleLambdaExecution');
     }
 
     if (!functionObject.vpc) functionObject.vpc = {};
@@ -117,7 +130,7 @@ class AwsCompileFunctions {
       .forEach((functionName) => this.compileFunction(functionName));
   }
 
-  // Helper functions
+  // helper functions
   cfLambdaFunctionTemplate() {
     return {
       Type: 'AWS::Lambda::Function',
diff --git a/lib/plugins/aws/deploy/lib/createStack.js b/lib/plugins/aws/deploy/lib/createStack.js
index 6ef503e9444..8258f364aa2 100644
--- a/lib/plugins/aws/deploy/lib/createStack.js
+++ b/lib/plugins/aws/deploy/lib/createStack.js
@@ -20,6 +20,7 @@ module.exports = {
       OnFailure: 'ROLLBACK',
       Capabilities: [
         'CAPABILITY_IAM',
+        'CAPABILITY_NAMED_IAM',
       ],
       Parameters: [],
       TemplateBody: JSON.stringify(this.serverless.service.provider
diff --git a/lib/plugins/aws/deploy/lib/mergeIamTemplates.js b/lib/plugins/aws/deploy/lib/mergeIamTemplates.js
index cb288637045..1003e383c24 100644
--- a/lib/plugins/aws/deploy/lib/mergeIamTemplates.js
+++ b/lib/plugins/aws/deploy/lib/mergeIamTemplates.js
@@ -10,7 +10,18 @@ module.exports = {
       return BbPromise.resolve();
     }
 
-    if (typeof this.serverless.service.provider.iamRoleARN !== 'string') {
+    let anyFunctionHasNoRole = false;
+    if (!('role' in this.serverless.service.provider)) {
+      this.serverless.service.getAllFunctions().forEach((functionName) => {
+        const functionObject = this.serverless.service.getFunction(functionName);
+        if (!('role' in functionObject)) {
+          anyFunctionHasNoRole = true;
+        }
+      });
+    }
+    if (!anyFunctionHasNoRole) return BbPromise.resolve();
+
+    if (typeof this.serverless.service.provider.role !== 'string') {
       // merge in the iamRoleLambdaTemplate
       const iamRoleLambdaExecutionTemplate = this.serverless.utils.readFileSync(
         path.join(this.serverless.config.serverlessPath,
diff --git a/lib/plugins/aws/lib/updateStack.js b/lib/plugins/aws/lib/updateStack.js
index 560c47dbfde..c0be9a84bf1 100644
--- a/lib/plugins/aws/lib/updateStack.js
+++ b/lib/plugins/aws/lib/updateStack.js
@@ -60,6 +60,7 @@ module.exports = {
       StackName: stackName,
       Capabilities: [
         'CAPABILITY_IAM',
+        'CAPABILITY_NAMED_IAM',
       ],
       Parameters: [],
       TemplateURL: templateUrl,


Test patch:
diff --git a/lib/plugins/aws/deploy/compile/events/stream/tests/index.js b/lib/plugins/aws/deploy/compile/events/stream/tests/index.js
index 9b1f03ac926..53fd4138330 100644
--- a/lib/plugins/aws/deploy/compile/events/stream/tests/index.js
+++ b/lib/plugins/aws/deploy/compile/events/stream/tests/index.js
@@ -63,6 +63,30 @@ describe('AwsCompileStreamEvents', () => {
       expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error);
     });
 
+    it('should not throw error or merge role statements if default policy is not present', () => {
+      awsCompileStreamEvents.serverless.service.functions = {
+        first: {
+          events: [
+            {
+              // doesn't matter if DynamoDB or Kinesis stream
+              stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1',
+            },
+          ],
+        },
+      };
+
+      // pretend that the default IamPolicyLambdaExecution is not in place
+      awsCompileStreamEvents.serverless.service.provider
+        .compiledCloudFormationTemplate.Resources
+        .IamPolicyLambdaExecution = null;
+
+      expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error);
+      expect(awsCompileStreamEvents.serverless.service.provider
+        .compiledCloudFormationTemplate.Resources
+        .IamPolicyLambdaExecution
+      ).to.equal(null);
+    });
+
     describe('when a DynamoDB stream ARN is given', () => {
       it('should create event source mappings when a DynamoDB stream ARN is given', () => {
         awsCompileStreamEvents.serverless.service.functions = {
diff --git a/lib/plugins/aws/deploy/compile/functions/tests/index.js b/lib/plugins/aws/deploy/compile/functions/tests/index.js
index 36f98972955..7bb65192602 100644
--- a/lib/plugins/aws/deploy/compile/functions/tests/index.js
+++ b/lib/plugins/aws/deploy/compile/functions/tests/index.js
@@ -82,9 +82,9 @@ describe('AwsCompileFunctions', () => {
         .to.deep.equal(`${s3Folder}/${s3FileName}`);
     });
 
-    it('should add iamRoleARN', () => {
+    it('should add an ARN provider role', () => {
       awsCompileFunctions.serverless.service.provider.name = 'aws';
-      awsCompileFunctions.serverless.service.provider.iamRoleARN = 'some:aws:arn:xxx:*:*';
+      awsCompileFunctions.serverless.service.provider.role = 'some:aws:arn:xxx:*:*';
       awsCompileFunctions.serverless.service.functions = {
         func: {
           handler: 'func.function.handler',
@@ -96,7 +96,180 @@ describe('AwsCompileFunctions', () => {
 
       expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
         .Resources.FuncLambdaFunction.Properties.Role
-      ).to.deep.equal(awsCompileFunctions.serverless.service.provider.iamRoleARN);
+      ).to.deep.equal(awsCompileFunctions.serverless.service.provider.role);
+    });
+
+    it('should add a logical role name provider role', () => {
+      awsCompileFunctions.serverless.service.provider.name = 'aws';
+      awsCompileFunctions.serverless.service.provider.role = 'LogicalNameRole';
+      awsCompileFunctions.serverless.service.functions = {
+        func: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func',
+        },
+      };
+
+      awsCompileFunctions.compileFunctions();
+
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.FuncLambdaFunction.Properties.Role
+      ).to.deep.equal({
+        'Fn::GetAtt': [
+          awsCompileFunctions.serverless.service.provider.role,
+          'Arn',
+        ],
+      });
+    });
+
+    it('should add a "Fn::GetAtt" Object provider role', () => {
+      awsCompileFunctions.serverless.service.provider.name = 'aws';
+      awsCompileFunctions.serverless.service.provider.role = {
+        'Fn::GetAtt': [
+          'LogicalRoleName',
+          'Arn',
+        ],
+      };
+      awsCompileFunctions.serverless.service.functions = {
+        func: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func',
+        },
+      };
+
+      awsCompileFunctions.compileFunctions();
+
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.FuncLambdaFunction.Properties.Role
+      ).to.deep.equal(awsCompileFunctions.serverless.service.provider.role);
+    });
+
+    it('should add an ARN function role', () => {
+      awsCompileFunctions.serverless.service.provider.name = 'aws';
+      awsCompileFunctions.serverless.service.functions = {
+        func: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func',
+          role: 'some:aws:arn:xxx:*:*',
+        },
+      };
+
+      awsCompileFunctions.compileFunctions();
+
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.FuncLambdaFunction.Properties.Role
+      ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func.role);
+    });
+
+    it('should add a logical role name function role', () => {
+      awsCompileFunctions.serverless.service.provider.name = 'aws';
+      awsCompileFunctions.serverless.service.functions = {
+        func: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func',
+          role: 'LogicalRoleName',
+        },
+      };
+
+      awsCompileFunctions.compileFunctions();
+
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.FuncLambdaFunction.Properties.Role
+      ).to.deep.equal({
+        'Fn::GetAtt': [
+          awsCompileFunctions.serverless.service.functions.func.role,
+          'Arn',
+        ],
+      });
+    });
+
+    it('should add a "Fn::GetAtt" Object function role', () => {
+      awsCompileFunctions.serverless.service.provider.name = 'aws';
+      awsCompileFunctions.serverless.service.functions = {
+        func: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func',
+          role: {
+            'Fn::GetAtt': [
+              'LogicalRoleName',
+              'Arn',
+            ],
+          },
+        },
+      };
+
+      awsCompileFunctions.compileFunctions();
+
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.FuncLambdaFunction.Properties.Role
+      ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func.role);
+    });
+
+    it('should prefer function declared role over provider declared role', () => {
+      awsCompileFunctions.serverless.service.provider.name = 'aws';
+      awsCompileFunctions.serverless.service.provider.role = 'some:provider:arn:xxx:*:*';
+      awsCompileFunctions.serverless.service.functions = {
+        func: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func',
+          role: 'some:function:arn:xxx:*:*',
+        },
+      };
+
+      awsCompileFunctions.compileFunctions();
+
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.FuncLambdaFunction.Properties.Role
+      ).to.equal(awsCompileFunctions.serverless.service.functions.func.role);
+    });
+
+    it('should add function declared roles', () => {
+      awsCompileFunctions.serverless.service.provider.name = 'aws';
+      awsCompileFunctions.serverless.service.functions = {
+        func0: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func0',
+          role: 'some:aws:arn:xx0:*:*',
+        },
+        func1: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func1',
+          role: 'some:aws:arn:xx1:*:*',
+        },
+      };
+
+      awsCompileFunctions.compileFunctions();
+
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.Func0LambdaFunction.Properties.Role
+      ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func0.role);
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.Func1LambdaFunction.Properties.Role
+      ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func1.role);
+    });
+
+    it('should add function declared role and fill in with provider role', () => {
+      awsCompileFunctions.serverless.service.provider.name = 'aws';
+      awsCompileFunctions.serverless.service.provider.role = 'some:aws:arn:xxx:*:*';
+      awsCompileFunctions.serverless.service.functions = {
+        func0: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func0',
+        },
+        func1: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func1',
+          role: 'some:aws:arn:xx1:*:*',
+        },
+      };
+
+      awsCompileFunctions.compileFunctions();
+
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.Func0LambdaFunction.Properties.Role
+      ).to.deep.equal(awsCompileFunctions.serverless.service.provider.role);
+      expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.Func1LambdaFunction.Properties.Role
+      ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func1.role);
     });
 
     it('should throw an error if the function handler is not present', () => {
@@ -420,4 +593,24 @@ describe('AwsCompileFunctions', () => {
       );
     });
   });
+
+  describe('#compileRole()', () => {
+    const logicalRoleName = 'LogicalRoleName';
+    const roleObject = {
+      'Fn::GetAtt': [logicalRoleName, 'Arn'],
+    };
+    const roleArn = 'some:aws:arn:xxx:*:*';
+
+    it('compiles a logical role name into an reference object', () => {
+      expect(awsCompileFunctions.compileRole(logicalRoleName)).to.deep.equal(roleObject);
+    });
+
+    it('returns a ARN string when given', () => {
+      expect(awsCompileFunctions.compileRole(roleArn)).to.equal(roleArn);
+    });
+
+    it('returns a reference object when given', () => {
+      expect(awsCompileFunctions.compileRole(roleObject)).to.deep.equal(roleObject);
+    });
+  });
 });
diff --git a/lib/plugins/aws/deploy/tests/createStack.js b/lib/plugins/aws/deploy/tests/createStack.js
index f544ff8cd11..ec005312836 100644
--- a/lib/plugins/aws/deploy/tests/createStack.js
+++ b/lib/plugins/aws/deploy/tests/createStack.js
@@ -61,7 +61,10 @@ describe('createStack', () => {
           {
             StackName: `${awsDeploy.serverless.service.service}-${awsDeploy.options.stage}`,
             OnFailure: 'ROLLBACK',
-            Capabilities: ['CAPABILITY_IAM'],
+            Capabilities: [
+              'CAPABILITY_IAM',
+              'CAPABILITY_NAMED_IAM',
+            ],
             Parameters: [],
             TemplateBody: JSON.stringify(coreCloudFormationTemplate),
             Tags: [{ Key: 'STAGE', Value: awsDeploy.options.stage }],
diff --git a/lib/plugins/aws/deploy/tests/mergeIamTemplates.js b/lib/plugins/aws/deploy/tests/mergeIamTemplates.js
index 76b86c2d05a..b11b8d2f54b 100644
--- a/lib/plugins/aws/deploy/tests/mergeIamTemplates.js
+++ b/lib/plugins/aws/deploy/tests/mergeIamTemplates.js
@@ -38,7 +38,7 @@ describe('#mergeIamTemplates()', () => {
     return awsDeploy.mergeIamTemplates()
       .then(() => {
         const resources = awsDeploy.serverless.service.provider
-                            .compiledCloudFormationTemplate.Resources;
+          .compiledCloudFormationTemplate.Resources;
 
         expect(resources.IamRoleLambdaExecution).to.equal(undefined);
         expect(resources.IamPolicyLambdaExecution).to.equal(undefined);
@@ -93,7 +93,6 @@ describe('#mergeIamTemplates()', () => {
   );
 
   it('should add custom IAM policy statements', () => {
-    awsDeploy.serverless.service.provider.name = 'aws';
     awsDeploy.serverless.service.provider.iamRoleStatements = [
       {
         Effect: 'Allow',
@@ -113,27 +112,6 @@ describe('#mergeIamTemplates()', () => {
       });
   });
 
-  it('should not add IamPolicyLambdaExecution if arn is provided', () => {
-    awsDeploy.serverless.service.provider.iamRoleARN = 'some:aws:arn:xxx:*:*';
-
-    return awsDeploy.mergeIamTemplates()
-      .then(() => expect(
-          awsDeploy.serverless.service.provider.compiledCloudFormationTemplate
-            .Resources.IamPolicyLambdaExecution
-        ).to.not.exist);
-  });
-
-
-  it('should not add IamRole if arn is provided', () => {
-    awsDeploy.serverless.service.provider.iamRoleARN = 'some:aws:arn:xxx:*:*';
-
-    return awsDeploy.configureStack()
-      .then(() => expect(
-          awsDeploy.serverless.service.provider.compiledCloudFormationTemplate
-            .Resources.IamRoleLambdaExecution
-        ).to.not.exist);
-  });
-
   it('should add a CloudWatch LogGroup resource', () => {
     awsDeploy.serverless.service.provider.cfLogs = true;
     const normalizedName = `${functionName[0].toUpperCase()}${functionName.substr(1)}LogGroup`;
@@ -261,4 +239,72 @@ describe('#mergeIamTemplates()', () => {
       );
     });
   });
+
+  it('should not add the default role and policy if all functions have an ARN role', () => {
+    awsDeploy.serverless.service.functions = {
+      func0: {
+        handler: 'func.function.handler',
+        name: 'new-service-dev-func0',
+        role: 'some:aws:arn:xx0:*:*',
+      },
+      func1: {
+        handler: 'func.function.handler',
+        name: 'new-service-dev-func1',
+        role: 'some:aws:arn:xx1:*:*',
+      },
+    };
+
+    awsDeploy.mergeIamTemplates().then(() => {
+      expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.IamPolicyLambdaExecution
+      ).to.equal(undefined);
+      expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.IamRoleLambdaExecution
+      ).to.equal(undefined);
+    });
+  });
+
+  it('should not add default role / policy if all functions have an ARN role', () => {
+    awsDeploy.serverless.service.provider.role = 'some:aws:arn:xxx:*:*';
+    awsDeploy.serverless.service.functions = {
+      func0: {
+        handler: 'func.function.handler',
+        name: 'new-service-dev-func0',
+        // obtain role from provider
+      },
+      func1: {
+        handler: 'func.function.handler',
+        name: 'new-service-dev-func1',
+        role: 'some:aws:arn:xx1:*:*',
+      },
+    };
+
+    awsDeploy.mergeIamTemplates().then(() => {
+      expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.IamPolicyLambdaExecution
+      ).to.equal(undefined);
+      expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.IamRoleLambdaExecution
+      ).to.equal(undefined);
+    });
+  });
+
+  it('should not add the IamPolicyLambdaExecution if role is defined on the provider level', () => {
+    awsDeploy.serverless.service.provider.role = 'some:aws:arn:xxx:*:*';
+
+    return awsDeploy.mergeIamTemplates()
+      .then(() => expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.IamPolicyLambdaExecution
+      ).to.not.exist);
+  });
+
+
+  it('should not add the IamRoleLambdaExecution if role is defined on the provider level', () => {
+    awsDeploy.serverless.service.provider.role = 'some:aws:arn:xxx:*:*';
+
+    return awsDeploy.mergeIamTemplates()
+      .then(() => expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources.IamRoleLambdaExecution
+      ).to.not.exist);
+  });
 });
diff --git a/lib/plugins/aws/tests/updateStack.js b/lib/plugins/aws/tests/updateStack.js
index 73ad2a87138..ffaca711de7 100644
--- a/lib/plugins/aws/tests/updateStack.js
+++ b/lib/plugins/aws/tests/updateStack.js
@@ -99,6 +99,7 @@ describe('updateStack', () => {
             StackName: `${awsDeploy.serverless.service.service}-${awsDeploy.options.stage}`,
             Capabilities: [
               'CAPABILITY_IAM',
+              'CAPABILITY_NAMED_IAM',
             ],
             Parameters: [],
             TemplateURL: `https://s3.amazonaws.com/${awsDeploy.bucketName}/${awsDeploy.serverless