4

Small question regarding how to use Spring Security to specify which client certificate can access what specific predefined endpoint, please.

By predefined endpoint, I mean the web application has default endpoint (not those I defined via @RestController) such as the actuator endpoint /actuator/health, /actuator/prometheus, or the Spring Cloud Config endpoints, such as /config/myservice/ There is no possibility to @PreAuthorize.

I would like to just specify which client certificate can access which endpoint, such as:

  • Client certificate with UID=Alice can access /actuator/health and /config/myservice.
  • Client certificate with UID=Bob can access /actuator/prometheus

There are many examples online on, How to extract X509 certificate:

But how to configure it in the application, i.e., this sort of mapping of which certificate can access what?

Thank you

PatPanda
  • 3,644
  • 9
  • 58
  • 154
  • Do you have a user store from where you lookup the authenticated user's detail in the `UserDetailsService`? If yes it should simply be case of having username to role mapping in database or file system somewhere and then in your `UserDetailsService.loadUserByUsername` method assign the user correct roles. As for how to protect specific urls be specific roles you can use `antMatcher` along with `hasRole/hasPermission` methods in `WebSecurityConfigurerAdapter`. – madteapot Apr 20 '21 at 13:42
  • Hello Setu, thanks for the comment. Unfortunately, no user store. And only if possible, I would like to avoid any sort of database interaction, and keep the solution simple. – PatPanda Apr 20 '21 at 13:50
  • Even without a userstore the principle remains same. In your `UserDetailsService.loadUserByUsername` have some if conditions which check the username and assign appropriate roles to Alice and Bob i.e. if username is Alice then add roles HEALTH and MYSERVICE else if username is Bob then add roles PROMETHEUS. Then in your `configure(HttpSecurity http)` method of `WebSecurityConfigurerAdapter` define `antMatchers` and `hasRole` conditions. Spring wouldn't directly provide a way to associate specific certificate with certain url access but using roles you can achieve this. – madteapot Apr 20 '21 at 14:13
  • Perhaps this question can be of some help. https://stackoverflow.com/questions/1102721/how-to-get-the-certificate-into-the-x509-filter-spring-security – Vanheden Apr 24 '21 at 06:42
  • definitely, thanks. I was also hoping besides how to parse the X509 (which Spring Security can do well), how to define, which certificate can access what route, with a code snippet if possible – PatPanda Apr 24 '21 at 07:51

1 Answers1

1

@Setu gave you the essential information to solve the problem.

Please, consider the following code adapted from the one that can be found in one of the articles you cited:

@SpringBootApplication
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509AuthenticationServer extends WebSecurityConfigurerAdapter {
  ...

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
        // Define the mapping between the different endpoints
        // and the corresponding user roles
        .antMatchers("/actuator/health").hasRole("ACTUATOR_HEALTH")
        .antMatchers("/actuator/prometheus").hasRole("ACTUATOR_PROMETEUS")
        .antMatchers("/config/myservice").hasRole("CONFIG_MYSERVICE")
        // Please, adjust the fallback as appropriate
        .anyRequest().authenticated()
      .and()
        // Configure X509Configurer (https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configurers/X509Configurer.html)
        .x509()
          .subjectPrincipalRegex("CN=(.*?)(?:,|$)")
          .userDetailsService(userDetailsService())
    ;
  }

  @Bean
  public UserDetailsService userDetailsService() {
    // Ideally this information will be obtained from a database or some
    // configuration information
    return new UserDetailsService() {

      @Override
      public UserDetails loadUserByUsername(String username) {

        Objects.requireNonNull(username);

        List<GrantedAuthority> authorities = null;
        switch (username) {
          // Match the different X509 certificate CNs. Maybe you can use the
          // X509 certificate subject distinguished name to include the role in
          // some way and obtain it directly with the subjectPrincipalRegex
          case "Alice":
            authorities = AuthorityUtils
              .commaSeparatedStringToAuthorityList("ROLE_ACTUATOR_HEALTH, ROLE_CONFIG_MYSERVICE");
            break;

          case "Bob":
            authorities = AuthorityUtils
              .commaSeparatedStringToAuthorityList("ROLE_ACTUATOR_PROMETHEUS");
            break;

          default:
            throw new UsernameNotFoundException(String.format("User '%s' not found!", username));
        }

        return new User(username, "", authorities);
      }
    };
  }
}

Please, adapt the code as you need to meet your actual endpoints and users.

jccampanero
  • 50,989
  • 3
  • 20
  • 49