Skip to content

Spring Security Observability for Authentication and Authorization #11595

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

Closed

Conversation

jzheaux
Copy link
Contributor

@jzheaux jzheaux commented Jul 18, 2022

By doing:

@Bean 
ObservationRegistry observationRegistry() {
    ObservationRegistry observationRegistry = ObservationRegistry.create();
    observationRegistry.observationConfig().observationHandler(new ObservationTextPublisher());
    return observationRegistry;
}

@Bean 
ApplicationListener<SecurityEvent> securityEventListener(ObservationRegistry registry) {
    return DelegatingObservationSecurityEventListener.withDefaults(registry).build();
}

Then all AuthenticationSuccessEvents, AuthenticationFailureEvents, and AuthorizationDeniedEvents will be published to Micrometer. AuthorizationGrantedEvents are not published by default in Spring Security. The event details will follow a specific schema. Also, all other SecurityEvents will be published as well, though only by name. Potentially, Spring Boot could construct a default DelegatingObservationSecurityEventListener instance like the above.

NOTE: all security events must have a prevailing observation to attach to. For a servlet-based application, this is the Security Filter Chain observation. In addition to the Security Filter Chain observation, there is one dedicated to authentication and another to authorization.

If you want to use the authorization observation, you will need to use AuthorizationFilter, which is activated by using authorizeHttpRequests:

@Bean
SecurityFilterChain app(HttpSecurity http) throws Exception {
	return http
		.httpBasic()
		.authorizeHttpRequests((request) -> request.anyRequest().authenticated())
		.build();
}

In that case, authorization events will be tied to the authorization observation.

Using the authentication observation is automatic as the default AuthenticationManager is wrapped.

Note: Micrometer's ObservationTextPublisher will log events to the console.

@jzheaux jzheaux added this to the 5.8.0-M2 milestone Jul 18, 2022
@jzheaux jzheaux added in: core An issue in spring-security-core type: enhancement A general enhancement labels Jul 18, 2022
@jzheaux jzheaux changed the title Spring Security Observability Spring Security Observability for Authentication and Authorization Jul 18, 2022
@jgrandja
Copy link
Contributor

jgrandja commented Jul 28, 2022

@jzheaux I took a look at the code but I couldn't figure out how a user could hook into observability using a custom SecurityEvent that the application has defined.

Would you be able to put together a test so I can better understand?

The use case is this:

  • An application has defined their own custom SecurityEvent called OAuth2AuthorizationConsentDeniedEvent. There is custom code they have hooked into for the authorization consent flow and are instantiating an OAuth2AuthorizationConsentDeniedEvent with all the relevant context.
  • What do they call to publish the event?
  • And what do they provide/configure to receive the event in order to build/format the appropriate message that will eventually be propagated to its destination?
  • How is the destination configured? For example, standard logging vs. a custom hook that gives me complete flexibility to further propagate the event/message to whatever destination I choose?

@jzheaux
Copy link
Contributor Author

jzheaux commented Jul 29, 2022

@jgrandja, great questions. I will put together a test. In the meantime, here are some quick answers:

What do they call to publish the event?

This PR does not add any publishing capabilities since it is based off of Spring's event-publishing infra. To publish a non-Spring Security event, use Spring's ApplicationEventPublisher. You can use ApplicationEventPublisher to publish Spring Security events as well -- or there are some existing conveniences classes for those that are likely simpler.

And what do they provide/configure to receive the event ... in order to build/format

@Bean 
ApplicationListener<SecurityEvent> securityEventListener(ObservationRegistry registry) {
    return DelegatingObservationSecurityEventListener.withDefaults(registry)
            .add(MyEvent.class, new OAuth2AuthorizationConsentDeniedEventKeyValuesProvider()).build();
}

Briefly, in Micrometer there is such a thing as an Observation.KeyValuesProvider. When creating a custom event, you will need to also create a key values provider that specifies to Micrometer what the relevant parts of that event are.

There is no formatting at this step in the flow.

How is the destination configured?

Micrometer has the concept of an ObservationHandler which takes care of shipping those observations to different destinations, like a log aggregator. You can see an example in the PR's description where it uses ObservationTextPublisher.

@rwinch rwinch self-assigned this Aug 2, 2022
@sjohnr sjohnr modified the milestones: 5.8.0-M2, 5.8.0-M3 Aug 15, 2022
@jzheaux jzheaux force-pushed the spring-security-observability branch 2 times, most recently from 4e365f5 to 92bdd09 Compare August 18, 2022 17:46
@jzheaux jzheaux modified the milestones: 5.8.0-M3, 6.0.0-M7 Aug 18, 2022
@jzheaux jzheaux changed the base branch from 5.8.x to main August 18, 2022 17:46
* @since 6.0
*/
public final class AuthenticationFailureEventKeyValuesProvider
implements Observation.KeyValuesProvider<SecurityEventObservationContext<AbstractAuthenticationFailureEvent>> {
Copy link
Member

Choose a reason for hiding this comment

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

We removed the concept of KeyValuesProvider and added a new one called ObservationConvention. It is similar to the KeyValuesProvider in a sense that it can provide low/high cardinality tags the same way but you can also specify the name and the contextual name of the observation if you want (most probably you don't need that).

spring-cloud/spring-cloud-gateway#2715

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds great. I updated the authentication observation to use ObservationConvention. As for the event handling, I think since I'm now calling observation.event, these classes don't make as much sense anymore as-is.

Please see ObservationSecurityEventListener for details. Essentially, an observation security event can hold a KeyValues instance to indicate which are the relevant attributes of the underlying SecurityEvent to publish.

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

public class DelegatingObservationSecurityEventListenerTests {
Copy link
Member

Choose a reason for hiding this comment

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

We have micrometer-observation-test that can help you to verify your interactions with the Observation API, it has a AssertJ-style DSL.

String name = "spring.security." + event.getEventType();
Observation.Context context = SecurityEventObservationContext.fromEvent(event);
Observation.createNotStarted(name, context, this.registry).keyValuesProvider(this.keyValuesProvider)
.observe(() -> {
Copy link
Member

Choose a reason for hiding this comment

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

What is the goal here?
What this will do is measuring the empty code block.

I'm just guessing but I think here signaling that something happened on an existing Observation would make more sense. E.g.:
somebody starts an Observation outside of Spring Security (e.g.: and HTTP request was received) and here we can signal that something happened during that observation:

Observation observation = observationRegistry.getCurrentObservation();
if (observation != null) {
    observation.event(Observation.Event.of("something.happened"));
}

Copy link
Member

Choose a reason for hiding this comment

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

I think measuring a code block is also a scenario that makes sense for Spring Security, e.g.: instrumenting AuthenticationProvider or any component that might need time to do its job, e.g.: using BCrypt, calling out on the network (http, ldap, jdbc, etc), reading files, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Excellent! Glad to hear that feature is available now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a place where the PR is in flux right now, but because of existing plans to deprecate AuthenticationProvider, instrumentation will likely go into an AuthenticationManager implementation instead of into AuthenticationProviders.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to AuthenticationFilter, which all the major authentication mechanisms will change over to in the 6.0 release.

@jzheaux
Copy link
Contributor Author

jzheaux commented Aug 31, 2022

@jgrandja based on some changes in Observability M4, I need to amend a few of my statements.

And what do they provide/configure to receive the event ... in order to build/format

First, if it extends SecurityEvent, you need do nothing. The event's name will be published to Micrometer by default.

Second, if you have event metadata that you want to transmit, then you can provide a strategy that computes that metadata for a given event, like so:

@Bean 
ApplicationListener<SecurityEvent> securityEventListener(ObservationRegistry registry) {
    Builder builder = DelegatingObservationSecurityEventListener.withDefaults(registry);

    // specify the custom class and the key values strategy
    builder.add(MyEvent.class, (event) -> KeyValues.of("my.key", event.getValue()));

    return builder.build();
}

There is no formatting at this step in the flow.

@jzheaux jzheaux force-pushed the spring-security-observability branch from 4dba3b1 to 30a61b2 Compare September 12, 2022 22:53
@marcusdacoregio marcusdacoregio modified the milestones: 6.0.0-M7, 6.0.0-RC1 Sep 16, 2022
@jzheaux jzheaux mentioned this pull request Sep 21, 2022
18 tasks
@jzheaux
Copy link
Contributor Author

jzheaux commented Sep 27, 2022

Closed in favor of #11906

@jzheaux jzheaux closed this Sep 27, 2022
@jzheaux jzheaux removed this from the 6.0.0-RC1 milestone Sep 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core An issue in spring-security-core type: enhancement A general enhancement
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

6 participants