Repository: localstack/localstack

Issue:
Cannot get S3 BucketPolicy created via CloudFormation
I created a CloudFormation stack using the template below via Boto3 Python method `cf_client.create_stack(StackName="test")`; however, when I attempt to call `s3_client.get_bucket_policy(Bucket="my-bucket")` I receive a `NoSuchBucketPolicy ... The bucket policy does not exist` error. 

```yaml
---
AWSTemplateFormatVersion: "2010-09-09"
Description: A sample template

Resources:
  TestBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-bucket
      AccessControl: PublicRead
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  TestBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket:
        Ref: TestBucket
      PolicyDocument:
        Id: TestBucketPolicy
        Statement:
          - Action:
              - "s3:GetObject"
            Effect: "Allow"
            Resource:
              Fn::Join:
                - ""
                - - "arn:aws:s3:::"
                  - Ref: TestBucket
                  - "/*"
            Principal: "*"
            Condition:
              StringLike:
                aws:Referer:
                  - "http://www.example.com/*"
                  - "http://example.com/*"
```

Is this something that's supported? 

Thanks in advance. 


Discussion:
As far as I know this kind of cloudformation functionality is not supported [code](https://github.com/localstack/localstack/blob/master/localstack/utils/cloudformation/template_deployer.py#L71). And if I understood correctly this kind of enhancement is non trivial to implement. Because boto client do not accept policy during creation :(

Resolving patch:
diff --git a/localstack/services/awslambda/lambda_executors.py b/localstack/services/awslambda/lambda_executors.py
--- a/localstack/services/awslambda/lambda_executors.py
+++ b/localstack/services/awslambda/lambda_executors.py
@@ -73,6 +73,11 @@ def is_java_lambda(lambda_details):
     return runtime in [LAMBDA_RUNTIME_JAVA8, LAMBDA_RUNTIME_JAVA11]
 
 
+def is_nodejs_runtime(lambda_details):
+    runtime = getattr(lambda_details, 'runtime', lambda_details)
+    return runtime.startswith('nodejs')
+
+
 class LambdaExecutor(object):
     """ Base class for Lambda executors. Subclasses must overwrite the _execute method """
 
@@ -229,6 +234,10 @@ def _execute(self, func_arn, func_details, event, context=None, version=None):
             command = ("bash -c 'cd %s; java %s -cp \"%s\" \"%s\" \"%s\" \"%s\"'" %
                 (taskdir, java_opts, classpath, LAMBDA_EXECUTOR_CLASS, handler, LAMBDA_EVENT_FILE))
 
+        # accept any self-signed certificates for outgoing calls from the Lambda
+        if is_nodejs_runtime(runtime):
+            environment['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'
+
         # determine the command to be executed (implemented by subclasses)
         cmd = self.prepare_execution(func_arn, environment, runtime, command, handler, lambda_cwd)
 
diff --git a/localstack/utils/cloudformation/template_deployer.py b/localstack/utils/cloudformation/template_deployer.py
--- a/localstack/utils/cloudformation/template_deployer.py
+++ b/localstack/utils/cloudformation/template_deployer.py
@@ -52,6 +52,15 @@ def lambda_get_params():
     return lambda params, **kwargs: params
 
 
+def rename_params(func, rename_map):
+    def do_rename(params, **kwargs):
+        values = func(params, **kwargs) if func else params
+        for old_param, new_param in rename_map.items():
+            values[new_param] = values.pop(old_param, None)
+        return values
+    return do_rename
+
+
 def get_nested_stack_name(params, **kwargs):
     stack_name = kwargs.get('stack_name', 'stack')
     return '%s-%s' % (stack_name, common.short_uid())
@@ -84,9 +93,9 @@ def select_parameters(*param_names):
     return lambda params, **kwargs: dict([(k, v) for k, v in params.items() if k in param_names])
 
 
-def dump_json_params(param_func, *param_names):
+def dump_json_params(param_func=None, *param_names):
     def replace(params, **kwargs):
-        result = param_func(params, **kwargs)
+        result = param_func(params, **kwargs) if param_func else params
         for name in param_names:
             if isinstance(result.get(name), (dict, list)):
                 # Fix for https://github.com/localstack/localstack/issues/2022
@@ -125,6 +134,12 @@ def replace(params, **kwargs):
             }
         }
     },
+    'S3::BucketPolicy': {
+        'create': {
+            'function': 'put_bucket_policy',
+            'parameters': rename_params(dump_json_params(None, 'PolicyDocument'), {'PolicyDocument': 'Policy'})
+        }
+    },
     'SQS::Queue': {
         'create': {
             'function': 'create_queue',
@@ -592,6 +607,10 @@ def retrieve_resource_details(resource_id, resource_status, resources, stack_nam
             bucket_name = resource_props.get('BucketName') or resource_id
             bucket_name = resolve_refs_recursively(stack_name, bucket_name, resources)
             return aws_stack.connect_to_service('s3').get_bucket_location(Bucket=bucket_name)
+        elif resource_type == 'S3::BucketPolicy':
+            bucket_name = resource_props.get('Bucket') or resource_id
+            bucket_name = resolve_refs_recursively(stack_name, bucket_name, resources)
+            return aws_stack.connect_to_service('s3').get_bucket_policy(Bucket=bucket_name)
         elif resource_type == 'Logs::LogGroup':
             # TODO implement
             raise Exception('ResourceNotFound')


Test patch: