Repository: serverless/serverless (JavaScript)
Date: 2019-10-12 02:36:32+00:00
Task type: Security

Issue:
Ensure AWS S3 buckets created by Serverless can't be accessed over HTTP
# This is a Feature Proposal

## Description

At a recent security audit I had done on some infrastructure, several of the S3 buckets created by the Serverless Framework showed up as not having `SecureTransport` flag set to true as part of the Bucket Policy.

While the buckets aren't created public, it's also (maybe?) a relatively simple change to enforce the policy.

The security team provided an example policy to apply to each newly created (or existing) bucket:

```json
{
    "Statement":[
        {
            "Action": "s3:*",
            "Effect":"Deny",
            "Principal": "*",
            "Resource":"arn:aws:s3:::<bucketname>/*",
            "Condition":{
                "Bool":
                { "aws:SecureTransport": false }
            }
        }
    ]
}
```

Similar or dependent issues:
* None



Issue discussion:
That's a good idea @a-h!
I would like to work on this feature if nobody minds.

Solution patch:
diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js
index 9fe9a4b1f76..a420b828bc1 100644
--- a/lib/plugins/aws/lib/naming.js
+++ b/lib/plugins/aws/lib/naming.js
@@ -299,6 +299,9 @@ module.exports = {
   getDeploymentBucketOutputLogicalId() {
     return 'ServerlessDeploymentBucketName';
   },
+  getDeploymentBucketPolicyLogicalId() {
+    return 'ServerlessDeploymentBucketPolicy';
+  },
   normalizeBucketName(bucketName) {
     return this.normalizeNameToAlphaNumericOnly(bucketName);
   },
diff --git a/lib/plugins/aws/package/lib/core-cloudformation-template.json b/lib/plugins/aws/package/lib/core-cloudformation-template.json
index 195b41fd7aa..cfef7c4d5a4 100644
--- a/lib/plugins/aws/package/lib/core-cloudformation-template.json
+++ b/lib/plugins/aws/package/lib/core-cloudformation-template.json
@@ -15,6 +15,31 @@
           ]
         }
       }
+    },
+    "ServerlessDeploymentBucketPolicy": {
+      "Type": "AWS::S3::BucketPolicy",
+      "Properties": {
+        "Bucket": {
+          "Ref": "ServerlessDeploymentBucket"
+        },
+        "PolicyDocument": {
+          "Statement": [
+            {
+              "Action": "s3:*",
+              "Effect": "Deny",
+              "Principal": "*",
+              "Resource": [
+                {
+                  "Fn::Join": ["", ["arn:aws:s3:::", { "Ref": "ServerlessDeploymentBucket" }, "/*"]]
+                }
+              ],
+              "Condition": {
+                "Bool": { "aws:SecureTransport": false }
+              }
+            }
+          ]
+        }
+      }
     }
   },
   "Outputs": {


Test patch:
diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js
index f288996d72b..9349b43c325 100644
--- a/lib/plugins/aws/lib/naming.test.js
+++ b/lib/plugins/aws/lib/naming.test.js
@@ -470,6 +470,14 @@ describe('#naming()', () => {
     });
   });
 
+  describe('#getDeploymentBucketPolicyLogicalId()', () => {
+    it('should return "ServerlessDeploymentBucketPolicy"', () => {
+      expect(sdk.naming.getDeploymentBucketPolicyLogicalId()).to.equal(
+        'ServerlessDeploymentBucketPolicy'
+      );
+    });
+  });
+
   describe('#normalizeBucketName()', () => {
     it('should remove all non-alpha-numeric characters and capitalize the first letter', () => {
       expect(sdk.naming.normalizeBucketName('b!u@c#k$e%t^N&a*m(e')).to.equal('BucketName');
diff --git a/lib/plugins/aws/package/lib/generateCoreTemplate.test.js b/lib/plugins/aws/package/lib/generateCoreTemplate.test.js
index 8a55751cc9b..915c0e28dce 100644
--- a/lib/plugins/aws/package/lib/generateCoreTemplate.test.js
+++ b/lib/plugins/aws/package/lib/generateCoreTemplate.test.js
@@ -45,6 +45,36 @@ describe('#generateCoreTemplate()', () => {
     };
   });
 
+  it('should reject non-HTTPS requests to the deployment bucket', () => {
+    return expect(awsPlugin.generateCoreTemplate()).to.be.fulfilled.then(() => {
+      const serverlessDeploymentBucketPolicy =
+        awsPlugin.serverless.service.provider.compiledCloudFormationTemplate.Resources
+          .ServerlessDeploymentBucketPolicy;
+
+      expect(serverlessDeploymentBucketPolicy).to.exist;
+      expect(serverlessDeploymentBucketPolicy.Type).to.equal('AWS::S3::BucketPolicy');
+      expect(serverlessDeploymentBucketPolicy.Properties).to.exist;
+      expect(serverlessDeploymentBucketPolicy.Properties.Bucket).to.deep.equal({
+        Ref: 'ServerlessDeploymentBucket',
+      });
+
+      expect(serverlessDeploymentBucketPolicy.Properties.PolicyDocument).to.exist;
+      expect(serverlessDeploymentBucketPolicy.Properties.PolicyDocument.Statement).to.exist;
+
+      expect(serverlessDeploymentBucketPolicy.Properties.PolicyDocument.Statement).to.deep.include({
+        Action: 's3:*',
+        Effect: 'Deny',
+        Principal: '*',
+        Resource: [
+          { 'Fn::Join': ['', ['arn:aws:s3:::', { Ref: 'ServerlessDeploymentBucket' }, '/*']] },
+        ],
+        Condition: {
+          Bool: { 'aws:SecureTransport': false },
+        },
+      });
+    });
+  });
+
   it('should use a custom bucket if specified', () => {
     const bucketName = 'com.serverless.deploys';