0
votes

Old Shared Tomcat Method

I have a custom SAML 2.0 authentication valve that I use currently in some standalone tomcat web servers to implement single sign on.

To implement this, we add the valve to the tomcat server's context.xml, like this (some example values used instead of real values):

<Context>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <Valve className="example.CustomIdpValve"
        idp="default" issuerName="SAMLIssuer" 
        idpUrl="https://example.idp.com/sso/"
        certificatePath="${catalina.base}/conf/idp.cer"
    />
</Context>

We would then control which endpoints need to be secured based on the web.xml.

New Spring Boot Embedded Tomcat Method

Now, we are using this valve in standalone spring boot applications running via the spring boot embedded tomcat. We add the same custom authentication valve in those applications via java configuration:

@Bean
public ConfigurableServletWebServerFactory embeddedServletContainerCustomizer() {
    final CustomIdpValve customIdpValve = new CustomIdpValve();
    final Valve singleSignOnValve = new SingleSignOn();

    final TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    factory.addContextValves(customIdpValve);
    factory.addContextValves(singleSignOnValve);

    return factory;
}

Then we require certain roles be included in the UserPrincipal in order to access the application. This is again set up through java configuration (the roles are pulled from application.properties):

@Component
public class WebAppInitializer implements ServletContextInitializer  {

    private final String[] roles;

    public WebAppInitializer(@Value("${tomcat.embedded.roles}") final String[] roles) {
        this.roles = roles;
    }

    public void onStartup(final ServletContext container) {
        final ServletRegistration.Dynamic servlet = (ServletRegistration.Dynamic) container.getServletRegistration("default");
        servlet.setServletSecurity(new ServletSecurityElement(new HttpConstraintElement(ServletSecurity.TransportGuarantee.NONE, roles)));
        servlet.setServletSecurity(new ServletSecurityElement(new HttpConstraintElement(ServletSecurity.TransportGuarantee.CONFIDENTIAL, roles)));
    }
}

The Problem

This works fine - the new spring boot applications will redirect any incoming request to the IdP for authentication, which then posts the SAML response back to the originally requested URL, and the page loads as expected.

The problem is, we have enabled spring boot actuator endpoints that need to bypass that authentication. Specifically, we need the health check endpoint /actuator/health to be unsecured so that it can be used as a readiness probe for kubernetes.

I am not able to determine a way to apply that ServletSecurity on a per-request path basis so that we don't require any authentication for any requests to the /actuator/* path.

Is something like this possible?

1

1 Answers

0
votes

Found a very simple solution.

If you change the management.server.port property to be a different port than your main application context runs on, spring boot will automatically create a child application context for the actuator endpoints.

This way, the ServletSecurity that I add only gets applied to the default parent application context, and the actuator endpoints running on the other port do not require authentication.