Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Security events by assuring we add identity and RoutingContext whenever available and help to locate security check with secured method property #40723

Merged

Conversation

michalvavrik
Copy link
Member

@michalvavrik michalvavrik commented May 20, 2024

fixes: #40170

Changes:

    • change: when proactive auth is enabled and identity available, add it to the Security event but never ever perform auth when only PermitAll check
    • logic behind change: the identity cannot be access from CDI request context when async observer is used as the next context is created
    • change: add RoutingContext to security event when security check is performed down on the CDI bean (not JAX-RS resource)
    • logic behind change:
      • users may not understand it is not there as Quarkus Security extension has no concept of Vert.x Routing Context
      • users cannot access it from the observer method as new CDI request context is created for async observer
      • even on CDI beans you want to have HTTP request context (like request path, headers, ...)
    • change: add class and method name to the security event properties when security checks are performed for standard security annotations
    • logic behind change:
      • when using standard security annotations on multiple levels (JAX-RS resource -> CDI bean -> ..?) different security checks are performed, for example endpoints requires @RolesAllowed({"user", "admin"}) and dynamically resolved bean requires @RolesAllowed("admin")
      • it is useful to know where the check failed and it cannot be derived from thrown exception stacktrace in same cases (reactive data types, ...)
  1. as I already needed to make SecurityConstrainer a synthetic bean, I also improved runtime config detection so that we avoid synchronized block when possible (that's just few lines)

if (event != null) {

if (event.user() instanceof QuarkusHttpUser user) {
return Map.of(RoutingContext.class.getName(), event, SecurityIdentity.class.getName(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@michalvavrik michalvavrik May 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have good eyes, this is the weakest place of this PR and I wasn't sure how to deal with it.

so the identity must come from Vert.x HTTP. I put it to the properties and I absolutely agree it's strange to have the identity in properties only in this scenario.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicating SecurityIdentity as one of the event properties is not a big problem.

What I don't understand is this: HttpAuthorizer already fires authorization failure events if the access was denied, and there it includes the identity, so, what case does this PR cover ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicating SecurityIdentity as one of the event properties is not a big problem.

cool, that was my thinking

What I don't understand is this: HttpAuthorizer already fires authorization failure events if the access was denied, and there it includes the identity, so, what case does this PR cover ?

Unsecured JAX-RS resources that calls CDI beans with standard security annotations. Inside JAX-RS endpoint you will never be able to statically analyze what CDI beans are going to be invoked (it depends on business logic).

If you want, you can have a look into the tests for it, it's the one called testNestedRolesAllowed and testNestedPermitAll, but I can also describe it for you. What is happening is that CDI beans are secured by CDI interceptors. and the CDI interceptors are inside Security extension that is not based on the Vert.x. In #40170 (comment) user described that they have requirement to log some actions for security audit and inside the issue description #40170 he says When a method (as opposed to a REST resource) is annotated with an RBAC annotation ..., please have a look.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it because the security events are not always covered at the HttpAuthorizer (quarkus-vertx-http-level) level only and sometimes at the quarkus-security level ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it because the security events are not always covered at the HttpAuthorizer (quarkus-vertx-http-level) level only and sometimes at the quarkus-security level ?

Yes, same as authorization. I can be even more specific:

@ApplicationScoped
public class SecuredBean {

  @RolesAllowed("admin")
  public String sayAdmin() {
     return "admin";
  }

}

In this example, the SecuredBean#sayAdmin is secured by CDI interceptor, not the HttpAuthorizer. It is not possible to use the HttpAuthorizer because the authorizer is for HTTP request, but this is method chain invocation. In other worlds, you do not know that SecuredBean#sayAdmin is going to be called.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to update AbstractSecurityEvent.getSecurityIdentity to check the properties if the identity property is null ? Otherwise we'd need to recommend users in the docs that you have to check both getIdentity and properties which may be a bit confusing for users

Copy link
Member Author

@michalvavrik michalvavrik May 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to update AbstractSecurityEvent.getSecurityIdentity to check the properties if the identity property is null ? Otherwise we'd need to recommend users in the docs that you have to check both getIdentity and properties which may be a bit confusing for users

I didn't think of this solution. What I did here

identity = (SecurityIdentity) additionalEventProps.get(SecurityIdentity.class.getName());
is that I copied the identity from properties to the io.quarkus.security.spi.runtime.AbstractSecurityEvent#securityIdentity, this way, AbstractSecurityEvent.getSecurityIdentity works (I have it tested).

What you suggest would work as well. I am open to changing it if you prefer that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you did looks better as it can only happen on this security constrainer path

Copy link

quarkus-bot bot commented May 20, 2024

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit aeba22d.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

You can consult the Develocity build scans.


Flaky tests - Develocity

⚙️ JVM Tests - JDK 17

📦 extensions/smallrye-reactive-messaging-kafka/deployment

io.quarkus.smallrye.reactivemessaging.kafka.deployment.dev.KafkaDevServicesDevModeTestCase.sseStream - History

  • Assertion condition defined as a Lambda expression in io.quarkus.smallrye.reactivemessaging.kafka.deployment.dev.KafkaDevServicesDevModeTestCase Expecting size of: [] to be greater than or equal to 2 but was 0 within 10 seconds. - org.awaitility.core.ConditionTimeoutException
org.awaitility.core.ConditionTimeoutException: 
Assertion condition defined as a Lambda expression in io.quarkus.smallrye.reactivemessaging.kafka.deployment.dev.KafkaDevServicesDevModeTestCase 
Expecting size of:
  []
to be greater than or equal to 2 but was 0 within 10 seconds.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:119)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:31)

📦 extensions/smallrye-reactive-messaging/deployment

io.quarkus.smallrye.reactivemessaging.hotreload.ConnectorChangeTest.testUpdatingConnector - History

  • Expecting actual: ["-4","-5","-6","-7","-8","-9","-10","-11"] to start with: ["-3", "-4", "-5", "-6"] - java.lang.AssertionError
java.lang.AssertionError: 

Expecting actual:
  ["-4","-5","-6","-7","-8","-9","-10","-11"]
to start with:
  ["-3", "-4", "-5", "-6"]

	at io.quarkus.smallrye.reactivemessaging.hotreload.ConnectorChangeTest.testUpdatingConnector(ConnectorChangeTest.java:36)

@sberyozkin sberyozkin merged commit 1a58477 into quarkusio:main May 21, 2024
51 checks passed
@quarkus-bot quarkus-bot bot added this to the 3.12 - main milestone May 21, 2024
@quarkus-bot quarkus-bot bot added the kind/enhancement New feature or request label May 21, 2024
@michalvavrik michalvavrik deleted the feature/improve-security-events branch May 21, 2024 09:46
@mrickly
Copy link

mrickly commented May 21, 2024

Thank you @michalvavrik , @sberyozkin ! 👍

@gsmet
Copy link
Member

gsmet commented May 21, 2024

This looks more like a new feature than a bugfix. It's more involved than I feel comfortable to backport.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

More complete SecurityEvents
4 participants