Repository: serverless/serverless (JavaScript)
Date: 2018-03-02 11:04:57+00:00
Task type: Feature

Issue:
Add support for AWS managed policies
# This is a Feature Proposal

## Description

Currently, I write lambda functions to create AWS accounts, aws roles, policies, ....  To be able to do this, the lambda functions needs almost all IAM actions. I would like to attach AWS managed policies (e.g. IAMFullAccess) instead of define the content of this policy again. Currently I have to add the following lines in the serverless.yml file:
```
iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "iam:*"
      Resource: "*"
```
I'd be grateful if we can attach AWS managed policies instead, for example:
```
iamRoleStatements:
    policies:
        - arn:aws:iam::aws:policy/IAMFullAccess
        - arn:aws:iam::aws:policy/AWSLambdaExecute
```

## Additional Data

* ***Serverless Framework Version you're using***: 1.14.0
* ***Operating System***: WIN7

Add support for AWS managed policies
# This is a Feature Proposal

## Description

Currently, I write lambda functions to create AWS accounts, aws roles, policies, ....  To be able to do this, the lambda functions needs almost all IAM actions. I would like to attach AWS managed policies (e.g. IAMFullAccess) instead of define the content of this policy again. Currently I have to add the following lines in the serverless.yml file:
```
iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "iam:*"
      Resource: "*"
```
I'd be grateful if we can attach AWS managed policies instead, for example:
```
iamRoleStatements:
    policies:
        - arn:aws:iam::aws:policy/IAMFullAccess
        - arn:aws:iam::aws:policy/AWSLambdaExecute
```

## Additional Data

* ***Serverless Framework Version you're using***: 1.14.0
* ***Operating System***: WIN7



Issue discussion:
I believe this is already implemented and can be closed. E.g.

```
myDefaultRole:
  Type: AWS::IAM::Role
  Properties: 
    ...
    ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
```

Please refer to documentation from [Serverless](https://serverless.com/framework/docs/providers/aws/guide/iam#one-custom-iam-role-for-all-functions) and [AWS](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-managepolicyarns)


This works if you want to define the IAM Role explicitly, however it would be good if you could do something like this so you don't have to write out all the defaults the framework provides for you: 
```
iamManagedPolicies:
  - arn:aws:iam::aws:policy/IAMFullAccess
  - arn:aws:iam::aws:policy/AWSLambdaExecute
iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "iam:*"
      Resource: "*"
```
I believe this is already implemented and can be closed. E.g.

```
myDefaultRole:
  Type: AWS::IAM::Role
  Properties: 
    ...
    ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
```

Please refer to documentation from [Serverless](https://serverless.com/framework/docs/providers/aws/guide/iam#one-custom-iam-role-for-all-functions) and [AWS](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-managepolicyarns)


This works if you want to define the IAM Role explicitly, however it would be good if you could do something like this so you don't have to write out all the defaults the framework provides for you: 
```
iamManagedPolicies:
  - arn:aws:iam::aws:policy/IAMFullAccess
  - arn:aws:iam::aws:policy/AWSLambdaExecute
iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "iam:*"
      Resource: "*"
```

Solution patch:
diff --git a/docs/providers/aws/guide/iam.md b/docs/providers/aws/guide/iam.md
index 19aacd6f389..9a0164cce21 100644
--- a/docs/providers/aws/guide/iam.md
+++ b/docs/providers/aws/guide/iam.md
@@ -45,7 +45,17 @@ provider:
              - "/*"
 
 ```
+Alongside `provider.iamRoleStatements` managed policies can also be added to this service-wide Role, define managed policies in `provider.iamManagedPolicies`. These will also be merged into the generated IAM Role so you can use `Join`, `Ref` or any other CloudFormation method or feature here too.
+```yml
+service: new-service
 
+provider:
+  name: aws
+  iamManagedPolicies:
+    - 'some:aws:arn:xxx:*:*'
+    - 'someOther:aws:arn:xxx:*:*'
+    - { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] }
+```
 ## Custom IAM Roles
 
 **WARNING:** You need to take care of the overall role setup as soon as you define custom roles.
diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md
index d06f4803e89..13892418487 100644
--- a/docs/providers/aws/guide/serverless.yml.md
+++ b/docs/providers/aws/guide/serverless.yml.md
@@ -62,6 +62,8 @@ provider:
       rateLimit: 100
   stackTags: # Optional CF stack tags
     key: value
+  iamManagedPolicies: # Optional IAM Managed Policies, which allows to include the policies into IAM Role
+    - arn:aws:iam:*****:policy/some-managed-policy
   iamRoleStatements: # IAM role statements so that services can be accessed in the AWS account
     - Effect: 'Allow'
       Action:
diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js
index a17e2dffd8a..12508e3d124 100644
--- a/lib/plugins/aws/package/lib/mergeIamTemplates.js
+++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js
@@ -7,6 +7,7 @@ const path = require('path');
 module.exports = {
   mergeIamTemplates() {
     this.validateStatements(this.serverless.service.provider.iamRoleStatements);
+    this.validateManagedPolicies(this.serverless.service.provider.iamManagedPolicies);
     return this.merge();
   },
 
@@ -120,6 +121,20 @@ module.exports = {
           .Statement.concat(this.serverless.service.provider.iamRoleStatements);
     }
 
+    if (this.serverless.service.provider.iamManagedPolicies) {
+      // add iam managed policies
+      const iamManagedPolicies = this.serverless.service.provider.iamManagedPolicies;
+      const resource = this.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources[this.provider.naming.getRoleLogicalId()].Properties;
+      if (iamManagedPolicies.length > 0) {
+        if (!_.has(resource, 'ManagedPolicyArns') || _.isEmpty(resource.ManagedPolicyArns)) {
+          resource.ManagedPolicyArns = [];
+        }
+        resource.ManagedPolicyArns = resource.ManagedPolicyArns
+          .concat(iamManagedPolicies);
+      }
+    }
+
     // check if one of the functions contains vpc configuration
     const vpcConfigProvided = [];
     this.serverless.service.getAllFunctions().forEach((functionName) => {
@@ -173,4 +188,14 @@ module.exports = {
       throw new this.serverless.classes.Error(errorMessage);
     }
   },
+
+  validateManagedPolicies(iamManagedPolicies) {
+    // Verify that iamManagedPolicies (if present) is an array
+    if (!iamManagedPolicies) {
+      return;
+    }
+    if (!_.isArray(iamManagedPolicies)) {
+      throw new this.serverless.classes.Error('iamManagedPolicies should be an array of arns');
+    }
+  },
 };


Test patch:
diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js
index 1407989251f..373f0aa32cb 100644
--- a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js
+++ b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js
@@ -155,6 +155,22 @@ describe('#mergeIamTemplates()', () => {
       });
   });
 
+  it('should add managed policy arns', () => {
+    awsPackage.serverless.service.provider.iamManagedPolicies = [
+      'some:aws:arn:xxx:*:*',
+      'someOther:aws:arn:xxx:*:*',
+      { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] },
+    ];
+    return awsPackage.mergeIamTemplates()
+      .then(() => {
+        expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[awsPackage.provider.naming.getRoleLogicalId()]
+          .Properties
+          .ManagedPolicyArns
+        ).to.deep.equal(awsPackage.serverless.service.provider.iamManagedPolicies);
+      });
+  });
+
   it('should throw error if custom IAM policy statements is not an array', () => {
     awsPackage.serverless.service.provider.iamRoleStatements = {
       policy: 'some_value',
@@ -222,6 +238,12 @@ describe('#mergeIamTemplates()', () => {
       .to.throw(/statement 0 is missing.*Resource; statement 2 is missing.*Effect, Action/);
   });
 
+  it('should throw error if managed policies is not an array', () => {
+    awsPackage.serverless.service.provider.iamManagedPolicies = 'a string';
+    expect(() => awsPackage.mergeIamTemplates())
+      .to.throw('iamManagedPolicies should be an array of arns');
+  });
+
   it('should add a CloudWatch LogGroup resource', () => {
     const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName);
     return awsPackage.mergeIamTemplates().then(() => {