Open faskan opened 2 years ago
/cc @matejvasek, @patriot1burke
Additional information: Note that it works fine with quarkus amazon rest api (quarkus-amazon-lambda-rest). I could see that the rest api implementation has an extra conditionevent.getRequestContext().getIdentity() != null
that makes it work. event.getRequestContext().getAuthorizer()
comes null for rest api as well.
private boolean isAuthenticatable(AwsProxyRequest event) {
Map<String, String> systemEnvironment = System.getenv();
boolean isSamLocal = Boolean.parseBoolean((String)systemEnvironment.get("AWS_SAM_LOCAL"));
String forcedUserName = (String)systemEnvironment.get("QUARKUS_AWS_LAMBDA_FORCE_USER_NAME");
return isSamLocal && forcedUserName != null || event.getRequestContext() != null && (event.getRequestContext().getAuthorizer() != null || event.getRequestContext().getIdentity() != null);
}
I am not sure if there is something equivalent to identity available from AWS api gateway for http api
I am having similar issue. Using AWS API Gateway (HTTP) with JWT + Cognito. We get an exception when we inject:
public class UsersResponseImpl implements UsersApi {
@Inject
javax.ws.rs.core.SecurityContext securityContext;
Exception trace:
[ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:2.11.3.Final:build (default) on project UsersApi: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR] [error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type javax.ws.rs.core.SecurityContext and qualifiers [@Default]
[ERROR] - java member: com.myapp.UsersResponseImpl#securityContext
[ERROR] - declared on CLASS bean [types=[com.myapp.openapi.app.api.UsersApi, java.lang.Object, com.myapp.UsersResponseImpl], qualifiers=[@Default, @Any], target=com.myapp.UsersResponseImpl]
[ERROR] at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1209)
[ERROR] at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:275)
[ERROR] at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:134)
[ERROR] at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:494)
[ERROR] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[ERROR] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR] at java.base/java.lang.reflect.Method.invoke(Method.java:566)
[ERROR] at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:977)
[ERROR] at io.quarkus.builder.BuildContext.run(BuildContext.java:281)
[ERROR] at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
[ERROR] at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
[ERROR] at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
[ERROR] at java.base/java.lang.Thread.run(Thread.java:829)
[ERROR] at org.jboss.threads.JBossThread.run(JBossThread.java:501)
[ERROR] Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type javax.ws.rs.core.SecurityContext and qualifiers [@Default]
[ERROR] - java member: com.myapp.UsersResponseImpl#securityContext
[ERROR] - declared on CLASS bean [types=[com.myapp.openapi.app.api.UsersApi, java.lang.Object, com.myapp.UsersResponseImpl], qualifiers=[@Default, @Any], target=com.myapp.UsersResponseImpl]
[ERROR] at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:411)
[ERROR] at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:532)
[ERROR] at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:263)
I also tried to inject java.security.Principal
but that does not seem to have any values.
@faskan Thanks for analysis, I'll have a look.
@vladaman I don't think your case is related, however you provided very little information. I'd suggest that you open an issue and ping me there. Anyway, using @Context
on resource method should work, e.g.
public String getUsername(@Context SecurityContext ctx) {
return ctx.getUserPrincipal().getName();
}
Thanks @michalvavrik
We've completely moved away from approach above. Rather we refactored our code with AWS HTTP API Gateway integration which does the JWT validation. We just parse APIGatewayV2HTTPEvent event and extract claims.
This works for us and gets job done: https://gist.github.com/vladaman/a7469213521f47575571c30c9b9b5aab
Hey @faskan ,
I think the behavior exactly matches documentation, I'll try to explain why:
Docs here says that with quarkus.lambda-http.enable-security=true
you enable security feature to securely invoke HTTP endpoints via API Gateway access control mechanism and table HTTP quarkus-amazon-lambda-http exactly shows where principal is taken from: requestContext.authorizer.jwt.claims.cognito:username or
requestContext.authorizer.iam.userId or requestContext.authorizer.lambda.principalId. If you test it locally, you can force user (as you mentioned), but otherwise only source of principal name are fields above. The feature is all about leveraging API Gateway support for controlling and managing access to your HTTP API. Access control happens before Quarkus lambda is even reached.
Your unit test is passing as you added authorizer, but you already know that.
You expect custom Identity provider to be called, but even custom identity provider needs identity source (in this case - authorizer field). If you want your own custom identity source that doesn't use API Gateway authorizers above, you simply need to use different authentication mechanism, or provide your own. Additionally docs even mention that custom identity provider https://quarkus.io/guides/amazon-lambda-http#custom-security-integration can be used to map security metadata (or request attributes) to security identity as AWS security only maps the principal name to Quarkus security APIs and does nothing to map claims or roles or permissions - hence the purpose of custom identity provider.
I followed Steps to reproduce and added Lambda authorizer that simply checked x-user
header and got call passing without NPE.
I am going to close the issue as in my eyes docs is pretty clear and you misunderstood what identity provider does. In case you don't agree or feels we should improve docs, or ... anything - just re-open the issue, no problem! Thanks
I think this ticket should be repoened. I can't get a CUSTOM lambda security integration to work at all. According to the docs here I should:
quarkus.lambda-http.enable-security=true
to my application.properties
LambdaIdentityProvider
and override the method public SecurityIdentity authenticate(APIGatewayV2HTTPEvent event)
I've done both of these things, and the source for the customer provider is below:
@ApplicationScoped
public class MyCustomProvider implements LambdaIdentityProvider {
@Inject
Logger LOG;
@Startup
public void onStartup() {
LOG.info("Starting custom provider");
}
@Override
public SecurityIdentity authenticate(APIGatewayV2HTTPEvent event) {
LOG.info("authenticate called");
if (event.getHeaders() == null || !event.getHeaders().containsKey("x-user"))
return null;
Principal principal = new QuarkusPrincipal(event.getHeaders().get("x-user"));
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
builder.setPrincipal(principal);
return builder.build();
}
}
When I start this up in dev mode (using quarkus dev
) I see the customer provider being created:
2024-09-11 10:47:04,345 INFO [com.pay.eve.val.MyCustomProvider] (Quarkus Main Thread) Starting custom provider
but when I hit the main http function lambda, nothing happens, there is no log message to say "authenticate called".
The docs linked about do not mention whether the http function lambda needs to be annotated in some way - my http handler function method signature looks like:
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response consume(String eventData) {
LOG.info("Got event: " + eventData.length());
return Response.ok().build();
}
and my reading of the docs for custom authentications would suggest that MyCustomProvider.authenticate() should be called before the http function consume() method. This does not happen. It could be that my understanding of the docs is incorrect, but if so it is not at all clear how a custom authenticator should work.
I think this ticket should be repoened. I can't get a CUSTOM lambda security integration to work at all.
I appreciate you have issue with a same title, but I closed it for good reasons, did you read my comment here https://github.com/quarkusio/quarkus/issues/24609#issuecomment-1327826314 ? I think it would be confusing to reopen same issue unless you have exactly same issue.
Would it be alright to open a new issue with reproducer (because your examples are not enough) and link it with this one, please? IMO your questions and examples describe different scenario. Thank you
@brucej72 please also ping me in a new issue, thank you
Hi @michalvavrik I think the issue is exactly the same as the original, and the reproducer code originally supplied here will demonstrate the problem. All I did was to add an injected logger to show that the authenticate method wasn't being called.
I don't really follow your point here. I know I can write a completely separate Lambda authenticator for API gateway, but the docs clearly suggest that I don't need to do this: I just need to enable security and them write a CDI bean that implements LambdaIdentityProvider, and then requests to the lambda http function will trigger my customer authenticator to be called:
"For HTTP, the important method to override is LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event). From this you will allocate a SecurityIdentity based on how you want to map security data from APIGatewayV2HTTPEvent"
However, this method is not called either in my code or in the original code supplied by @faskan when running under quarkus dev
and hitting the (local) lambda endpoint with a browser or postman.
@brucej72 I have reopened it for you.
Sorry you don't understand my explanation, but I still believe that situation is clear. The identity provider you supply is only invoked when it is authenticable https://github.com/quarkusio/quarkus/blob/main/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpAuthenticationMechanism.java#L71. It will never be authenticable when the authorizer is empty. I even tried it judging by the comment I left here almost 2 years ago. Documentation also seems clear to me, it says: From this you will allocate a SecurityIdentity based on how you want to map security data from AwsProxyRequest So you cannot do mapping if you don't have security data.
Personally I think this is not a bug, but a feature request and you would be better off with a new issue. However I am not engineer responsible for Quarkus Amazon Lambda HTTP and I can be easily wrong. Please consider everything that I wrote as my personal opinion and nothing more.
@patriot1burke please have a look into this, thank you
Hi @michalvavrik the point I took away from the docs was that if I have access to the the APIGatewayV2HTTPEvent object (via LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event) I can implement my own auth based on whatever, for example an HMAC scheme. However, the authenticate method is never called, so I can only suggest that this is either a bug or that the docs are incorrect in what they suggest is possible. I was also thinking about the line below the one you linked: https://github.com/quarkusio/quarkus/blob/e1577e6f462b325ca050f6326b5cb7fffab91b10/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpAuthenticationMechanism.java#L72
If I provide my own implementation of LambdaIdentityProvider, would you not expect (event.getRequestContext() != null && event.getRequestContext().getAuthorizer() != null)
to be true, and therefore this be authenticabable?
If I provide my own implementation of LambdaIdentityProvider, would you not expect (event.getRequestContext() != null && event.getRequestContext().getAuthorizer() != null) to be true, and therefore this be authenticabable?
No, I would not, because this has nothing to do with your LambdaIdentityProvider
, you can read a code and determine why your provider is not called.
Hi @michalvavrik the point I took away from the docs was that if I have access to the the APIGatewayV2HTTPEvent object (via LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event) I can implement my own auth based on whatever, for example an HMAC scheme. However, the authenticate method is never called, so I can only suggest that this is either a bug or that the docs are incorrect in what they suggest is possible.
I don't know how else to explain why is it not called. Let's move on towards your options until this ticket is addressed by someone else.
If you want to make this work until then, here is how to do that:
HttpAuthenticationMechanism
as documented here https://quarkus.io/version/3.8/guides/security-customization#httpauthenticationmechanism-customizationLambdaHttpAuthenticationMechanism
IMO this should be improved for users to avoid all these steps. I hope this helps.
Thank you @michalvavrik this does help a lot.
I was not aware that I could implement HttpAuthenticationMechanism
and set my implementation to have a higher priority and then get the the APIGatewayV2HTTPEvent object in this way. I think the fault lies in the documentation and perhaps the section here should reference this with an explanation that it is possible to provide a completely custom solution to authenticate the http lambda function in the way you suggest.
ping @patriot1burke
Describe the bug
My custom identity provider is not getting called when a quarkus lambda http api is deployed in AWS. My test case works fine. And in SAM local it works only when setting QUARKUS_AWS_LAMBDA_FORCE_USER_NAME environment variable. In AWS it doesn't work at all. When I curl the API gateway endpoint, I simply get a NullPointerException on my resource class just because the security context is null. I added loggers to my custom identity provider and found that the provider is not getting called.
I am using the quarkus generated sam.*.yaml file to deploy my lambda to AWS. I've tried both jvm and native sam files, but both produce the same result.
I am expecting quarkus to automatically discover any custom identity provider and invoke it in Lambda authentication mechanism. I did some investigation and I am a bit puzzled about the below condition in LambdaHttpAuthenticationMechanism.isAuthenticatable method.
event.getRequestContext() != null && event.getRequestContext().getAuthorizer() != null
It seems like my custom identity provider is not getting called because of this condition. Is quarkus expecting a custom lambda authorizer on top of the custom LambdaIdentityProvider?
Here is my complete source code. The below property is enabled in application.properties
quarkus.lambda-http.enable-security=true
Expected behavior
Custom Identity provider should be called
Actual behavior
Custom identify provider is not getting called.
How to Reproduce?
Output of
uname -a
orver
No response
Output of
java -version
No response
GraalVM version (if different from Java)
No response
Quarkus version or git rev
2.7.5.Final
Build tool (ie. output of
mvnw --version
orgradlew --version
)Apache Maven 3.8.2
Additional information
No response