Apigateway v2 Authorizer IAM policy evaluation doesnt work on Effect 'Deny'

Hi,
I have setup a custom authorizer to return the following policy:

{
  "Effect": "Deny",
  "Action": [
    "execute-api:Invoke"
  ],
  "Resource": [
    "arn:aws:execute-api:{{region}}:{{account-id}}:{{api-id}}/{{stage}}/GET/petstore/v1/pets"
  ]
},
{
  "Effect": "Allow",
  "Action": [
    "execute-api:Invoke"
  ],
  "Resource": [
    "arn:aws:execute-api:{{region}}:{{account-id}}:{{api-id}}/{{stage}}/GET/petstore/v2/pets"
  ]
}

but I always end up with an exception:

 [   asgi_gw_4] l.s.apigateway.authorizers : Unexpected Effect=Deny (expected: "Allow") in Lambda authorizer response
localstack_main  | 2023-09-01T15:20:43.323 DEBUG --- [   asgi_gw_4] l.s.apigateway.authorizers : Authorization denied: {"message":"Forbidden"} - Traceback (most recent call last):
localstack_main  |   File "/opt/code/localstack/.venv/lib/python3.10/site-packages/localstack_ext/services/apigateway/authorizers.py.enc", line 339, in is_request_authorized
localstack_main  |     try:AuthorizerService().check_request_authorization(invocation_context)
localstack_main  |   File "/opt/code/localstack/.venv/lib/python3.10/site-packages/localstack_ext/services/apigateway/authorizers.py.enc", line 334, in check_request_authorization
localstack_main  |     D=E.authorize(A)
localstack_main  |   File "/opt/code/localstack/.venv/lib/python3.10/site-packages/localstack_ext/services/apigateway/authorizers.py.enc", line 184, in authorize
localstack_main  |     def authorize(B,invocation_context):C=invocation_context;D=B._get_authorizer_lambda_arn();E=connect_to(region_name=extract_region_from_arn(D)).awslambda;F=B._create_authorizer_event(C);G=E.invoke(FunctionName=D,Payload=to_bytes(json.dumps(F)));A=G.get(_b);A=to_str(A.read())if A else'';LOG.info('Received authorizer result: %s',A);return B.verify_response_policy(A,C)
localstack_main  |   File "/opt/code/localstack/.venv/lib/python3.10/site-packages/localstack_ext/services/apigateway/authorizers.py.enc", line 169, in verify_response_policy
localstack_main  |     if D.lower()!='allow':LOG.debug(f'Unexpected Effect={D} (expected: "Allow") in Lambda authorizer response');K={_P:H}if invocation_context.is_v1()else{_B:G};raise DeniedAuthorization(json.dumps(K,separators=(_C,_F)))
localstack_main  | localstack_ext.services.apigateway.authorizers.DeniedAuthorization: {"message":"Forbidden"}

I have looked around for similar issues but can’t find anything on this, surely, gateway needs to evaluate a ‘Deny’ effect too but it seems like that isn’t allowed ?

I simply want to allow/disallow calling certain end points like mentioned here.

Runtime version │ 2.1.1.dev | localstack-pro
│ Docker image │ tag: latest, id: 9cf5d6c2cb02 │

Would appreciate some feedback please.

Hi @path2light23,

Please have a look at our terraform examples for ApiGatewayv2. I hope this helps with the configuration of your deployment.

Hi @Marcel,
Thanks for the response. They appear to be identical but with difference being AuthorizerPayloadFormatVersion: ‘1.0’ instead of 2.0.

If I change it to latter I get deserialization error on my lambda function- function has APIGatewayV2CustomAuthorizerEvent as an input parameter which has property private String rawQueryString; but gateway seems to pass a value of rawQueryString: {} instead.

Sending invoke-payload '{“invoke-id”: “1f65fd82-7400-480f-bd76-1bdb95272008”, “invoked-function-arn”: “arn:aws:lambda:us-east-1:000000000000:function:cognito-ApiGatewayAuthorizedLamb-23a65d6e”, “payload”: "{"version": "2.0", "type": "REQUEST", "routeArn": "arn:aws:execute-api:us-east-1:000000000000:b9fd4c1f/$default/GET/auth/v2/test", , "routeKey": "ANY /auth/v2/test", "rawPath": "/auth/v2/test", "rawQueryString": {}, "cookies": null, "headers": {…}

[   asgi_gw_2] l.s.l.i.version_manager    : > Caused by: java.io.UncheckedIOException: com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
localstack_main  | 2023-09-04T11:01:59.382 DEBUG --- [   asgi_gw_2] l.s.l.i.version_manager    : >  at [Source: (ByteArrayInputStream); line: 1, column: 1192] (through reference chain: com.amazonaws.services.lambda.runtime.events.APIGatewayV2CustomAuthorizerEvent["rawQueryString"])

I am really stumped now.

Hello @path2light23,

Regarding your last post, there is a confirmed issue with the serialisation of the APIGatewayV2CustomAuthorizerEvent, and a fix is on progress.

Could you confirm that your code works against AWS? In that case, maybe something could be missing in LocalStack.

But for your initial post, I’ve read the link you provided, and I think you could simplify the code a lot.
There’s this sentence there:

For a more granular access control to your APIs, you may want to consider using AWS API Gateway resource policies or Lambda authorizers, as IAM policies may not provide the granularity you’re looking for. If you do this, you’d be able to write custom logic to allow or deny access to certain routes. For instance, with a Lambda authorizer, you could extract the appointment_id from the path parameters and determine if the current user should have access to it.

The given IAM policy from the link is not to be returned by the lambda authorizer (I am not sure a Lambda Authorizer can return an IAM policy with multiple statements, I cannot find example online and would need to test against AWS, but it would not make a lot of sense). This was an IAM policy for a given user.

An IAM policy returned by a lambda authorizer must explicitly allow or deny a request to the particular resource that was sent:

Lambda authorizers must return an IAM policy that allows or denies access to your API route

When using lambda authorizer, you can yourself write the logic to explicitly allow or deny the request by yourself checking if from your example there is v1 or v2 in the path. Then you would return, depending on the payload format version v1.0 or v2.0, either an IAM policy with one statement with an Allow or Deny effect, or a simplified response indicated if the request is authorized or not if using v2.0.

Hi @bentsku,
Thanks for the response, I have reverted back to payload 1.0 for the time being. So the issue I am facing at present is: It appears like the gateway policy evaluation is different than AWS. For example I wish to allow only GET requests , but the GW on localstack allows me to call PUT api, even though I only have explicit Allow on GET.

So the following policy:

{
“Effect”: “Allow”,
“Action”: [
“execute-api:Invoke”
],
“Resource”: [
“arn:aws:execute-api:{{region}}:{{account-id}}:{{api-id}}/*/GET{{resource-path}}”
]
}

still allows me to call /*/PUT/{{resource-path}} when it shouldn’t.

Regarding multiple statements, AWS seems to support this (notice the pets api example here link)

I don’t have access to an AWS account yet, so can’t check the behaviour there.

Yes, it seems the Resource is not evaluated for the policy, only the Effect field will have an effect for now. I’ll put this in our backlog. We will need to test against AWS and implement the proper behaviour.

In the meantime, you could explicitly deny the request by manually checking the HTTP method from the request and returning a Deny policy for if it’s not GET. Sorry for the inconvenience.

Thanks for all the help and confirming this. :slightly_smiling_face: