Repository: localstack/localstack
Pull request: adds authorizer object to lambda event
This PR adds the authorizer object to the lambda event and encapsulates a new property into the invocation context to represent the authorization type (lambda, iam, jwt)

Fixes #5130 


Problem statement:
bug: fails to invoke lambda in docker
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

I'm using localstack in docker-compose to test a lambda function. The lambda is in a docker image and gets triggered when a file is uploaded to s3. 

Here is the localstack docker-compose setup
```yml
localstack:
    image: localstack/localstack:latest
    ports:
      - 4566:4566
    environment:
      SERVICES: s3,sns,lambda,iam
      AWS_DEFAULT_REGION: us-west-2
      LAMBDA_EXECUTOR: docker-reuse
      LAMBDA_REMOVE_CONTAINERS: 0
      LOCALSTACK_API_KEY: ${LOCALSTACK_API_KEY}
    volumes:
     - ./localstack-init-scripts:/docker-entrypoint-initaws.d
     - "/var/run/docker.sock:/var/run/docker.sock"
```

And here is the localstack error that happens when the lambda is triggered.
```bash
localstack_1        | 2021-12-15T20:09:24.960:ERROR:localstack_ext.services.awslambda.lambda_extended: Unable to invoke Lambda "arn:aws:lambda:us-west-2:000000000000:function:kb-service-upload-handler": 
localstack_1        | Traceback (most recent call last):
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/docker/api/client.py", line 268, in _raise_for_status
localstack_1        |     response.raise_for_status()
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/requests/models.py", line 943, in raise_for_status
localstack_1        |     raise HTTPError(http_error_msg, response=self)
localstack_1        | requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: http+docker://localhost/v1.41/containers/cea0db10eafcec866aaedb6d45048e060f978d44843dae4e9282a1fb75340837/start
localstack_1        | 
localstack_1        | During handling of the above exception, another exception occurred:
localstack_1        | 
localstack_1        | Traceback (most recent call last):
localstack_1        |   File "/opt/code/localstack/localstack/utils/docker_utils.py", line 1413, in start_container
localstack_1        |     container.start()
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/docker/models/containers.py", line 404, in start
localstack_1        |     return self.client.api.start(self.id, **kwargs)
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/docker/utils/decorators.py", line 19, in wrapped
localstack_1        |     return f(self, resource_id, *args, **kwargs)
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/docker/api/container.py", line 1111, in start
localstack_1        |     self._raise_for_status(res)
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/docker/api/client.py", line 270, in _raise_for_status
localstack_1        |     raise create_api_error_from_http_exception(e)
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/docker/errors.py", line 31, in create_api_error_from_http_exception
localstack_1        |     raise cls(e, response=response, explanation=explanation)
localstack_1        | docker.errors.APIError: 400 Client Error for http+docker://localhost/v1.41/containers/cea0db10eafcec866aaedb6d45048e060f978d44843dae4e9282a1fb75340837/start: Bad Request ("OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "/tmp/e965911c.sh": stat /tmp/e965911c.sh: no such file or directory: unknown")
localstack_1        | 
localstack_1        | During handling of the above exception, another exception occurred:
localstack_1        | 
localstack_1        | Traceback (most recent call last):
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/localstack_ext/services/awslambda/lambda_extended.py.enc", line 458, in do_run_lambda_executor
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/localstack_ext/services/awslambda/lambda_launcher.py.enc", line 343, in docker_reuse_execute_in_container
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/localstack_ext/services/awslambda/lambda_launcher.py.enc", line 242, in custom_image_execute_in_container
localstack_1        |   File "/opt/code/localstack/localstack/utils/docker_utils.py", line 1541, in run_container
localstack_1        |     result = self.start_container(
localstack_1        |   File "/opt/code/localstack/localstack/utils/docker_utils.py", line 1418, in start_container
localstack_1        |     raise ContainerException()
localstack_1        | localstack.utils.docker_utils.ContainerException
localstack_1        | 2021-12-15T20:09:24.960:INFO:localstack_ext.services.awslambda.lambda_extended: Lambda container output: None
localstack_1        | None
```

The lambda is getting created like this
```bash
awslocal lambda create-function \
  --function-name kb-service-upload-handler \
  --code ImageUri=kb-service-upload-handler \
  --role arn:aws:iam::000000000000:role/test \
  --handler "src.upload_handler.main.handler" \
  --package-type Image
```

It appears the code inside the lambda never gets invoked.

### Expected Behavior

The lambda should be invoked successfully.

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)
```
    docker-compose up
```
#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

```
awslocal lambda create-function \
  --function-name kb-service-upload-handler \
  --code ImageUri=kb-service-upload-handler \
  --role arn:aws:iam::000000000000:role/test \
  --handler "src.upload_handler.main.handler" \
  --package-type Image

awslocal s3api put-bucket-notification-configuration \
  --bucket client-uploads \
  --notification-configuration file://notification.json
```


### Environment

```markdown
- OS: Ubuntu
- LocalStack: latest
```


### Anything else?

_No response_

Hints from issue discussion:
Update: I got past the above error by using `HOST_TMP_FOLDER`, but now the function times out and there are no logs from it.

```yml
  localstack:
    image: localstack/localstack:latest
    ports:
      - 4566:4566
    environment:
      SERVICES: s3,sns,lambda,iam
      AWS_DEFAULT_REGION: us-west-2
      LAMBDA_EXECUTOR: docker
      LAMBDA_REMOVE_CONTAINERS: 0
      LOCALSTACK_API_KEY: ${LOCALSTACK_API_KEY}
      LAMBDA_REMOTE_DOCKER: "true"
      HOST_TMP_FOLDER: /tmp/localstack
      DEBUG: 1
    volumes:
     - ./localstack-init-scripts:/docker-entrypoint-initaws.d
     - "/var/run/docker.sock:/var/run/docker.sock"
     - /tmp/localstack:/tmp/localstack
 ```

Error message:
```bash
localstack_1        | 2021-12-15T22:20:11.254:ERROR:localstack_ext.services.awslambda.lambda_extended: Unable to invoke Lambda "arn:aws:lambda:us-west-2:000000000000:function:kb-service-upload-handler": 
localstack_1        | Traceback (most recent call last):
localstack_1        |   File "/opt/code/localstack/.venv/lib/python3.8/site-packages/localstack_ext/services/awslambda/lambda_extended.py.enc", line 462, in do_run_lambda_executor
localstack_1        |   File "/usr/local/lib/python3.8/queue.py", line 178, in get
localstack_1        |     raise Empty
localstack_1        | _queue.Empty
localstack_1        | 2021-12-15T22:20:11.260:DEBUG:localstack_ext.services.awslambda.lambda_extended: Log output for invocation of Lambda "kb-service-upload-handler": INIT: Using Lambda API Runtime target host: 'kb-service-upload-handler.us-west-2.localhost.localstack.cloud:4566'
localstack_1        | INIT: Host 'kb-service-upload-handler.us-west-2.localhost.localstack.cloud' resolves to '172.27.0.3'
localstack_1        | [ERROR] [1639606802454] LAMBDA_RUNTIME Failed to get next invocation. No Response from endpoint
localstack_1        | Traceback (most recent call last):
localstack_1        |   File "/var/runtime/bootstrap.py", line 60, in <module>
localstack_1        |     main()
localstack_1        |   File "/var/runtime/bootstrap.py", line 57, in main
localstack_1        |     awslambdaricmain.main([os.environ["LAMBDA_TASK_ROOT"], os.environ["_HANDLER"]])
localstack_1        |   File "/var/runtime/awslambdaric/__main__.py", line 16, in main
localstack_1        |     bootstrap.run(app_root, handler, lambda_runtime_api_addr)
localstack_1        |   File "/var/runtime/awslambdaric/bootstrap.py", line 399, in run
localstack_1        |     event_request = lambda_runtime_client.wait_next_invocation()
localstack_1        |   File "/var/runtime/awslambdaric/lambda_runtime_client.py", line 68, in wait_next_invocation
localstack_1        |     response_body, headers = runtime_client.next()
localstack_1        | RuntimeError: Failed to get next
localstack_1        | 
```

Hi!
Using custom lambdas currently requires first the mount you already discovered, and secondly connection between the Lambda container and Localstack itself, which seems to be broken.
Please try setting `LAMBDA_DOCKER_NETWORK=<your_docker_compose_network>`. The name of your docker compose network is normally displayed on docker-compose up, and is usually `<PROJECT_NAME>_default` (project name is the parent folder of your docker compose file, unless configured otherwise.
Please let me know if that helps, thanks!
@dfangl That did the trick. I just wish it had been documented that LAMBDA_DOCKER_NETWORK is required. Thank you :pray: 
I filed a PR adding a hint for this options in the get-started section of the docs.
We are working on a better solution for this though, and hope to mitigate this issue soon!

Commit message: adds authorizer object to lambda event

Patch to review:
diff --git a/localstack/services/apigateway/apigateway_listener.py b/localstack/services/apigateway/apigateway_listener.py
index f082f78dfd492..2fd46e31e5443 100644
--- a/localstack/services/apigateway/apigateway_listener.py
+++ b/localstack/services/apigateway/apigateway_listener.py
@@ -555,6 +555,12 @@ def invoke_rest_api_integration_backend(invocation_context: ApiInvocationContext
 
             invocation_context.context = get_event_request_context(invocation_context)
             invocation_context.stage_variables = helpers.get_stage_variables(invocation_context)
+            if invocation_context.authorizer_type:
+                authorizer_context = {
+                    invocation_context.authorizer_type: invocation_context.auth_context
+                }
+                invocation_context.context["authorizer"] = authorizer_context
+
             request_templates = RequestTemplates()
             payload = request_templates.render(invocation_context)
 
diff --git a/localstack/services/apigateway/context.py b/localstack/services/apigateway/context.py
index 28b358fb525fa..f0b224273fdf5 100644
--- a/localstack/services/apigateway/context.py
+++ b/localstack/services/apigateway/context.py
@@ -132,6 +132,13 @@ def auth_identity(self) -> Optional[Dict]:
                 self.auth_info["identity"] = {}
             return self.auth_info["identity"]
 
+    @property
+    def authorizer_type(self) -> str:
+        if isinstance(self.auth_info, dict):
+            if self.auth_info.get("authorizer_type") is None:
+                self.auth_info["authorizer_type"] = {}
+            return self.auth_info["authorizer_type"]
+
     def is_websocket_request(self):
         upgrade_header = str(self.headers.get("upgrade") or "")
         return upgrade_header.lower() == "websocket"


Code review comments:
diff_hunk: @@ -132,6 +132,13 @@ def auth_identity(self) -> Optional[Dict]:
                 self.auth_info["identity"] = {}
             return self.auth_info["identity"]
 
+    @property
+    def authorizer_type(self) -> str:
+        if isinstance(self.auth_info, dict):
+            if self.auth_info.get("authorizer_type") is None:
+                self.auth_info["authorizer_type"] = {}
path: localstack/services/apigateway/context.py
text: @user1:
We're setting the `authorizer_type` to `{}` here, although the type hint indicates that a string gets returned (not `dict`). Should we simply `return self.auth_info.get("authorizer_type")` from the method here? 

@author:
Yep, should be string not `dict`