Skip to content

Commit 79dedfd

Browse files
CarsonCookjandadav
andauthored
feat: Catalog: authenticate with client certificate for /apidoc/** endpoints (#1568)
* Add integration tests Signed-off-by: Carson Cook <carson.cook@ibm.com> * Add x509 cert for all ac endpoints Signed-off-by: Carson Cook <carson.cook@ibm.com> * Configure cert for only apidoc route Signed-off-by: Carson Cook <carson.cook@ibm.com> * Add cert for not apidoc route integration test Signed-off-by: Carson Cook <carson.cook@ibm.com> * Add AT-TLS filter Signed-off-by: Carson Cook <carson.cook@ibm.com> * Make verifying cert configurable Signed-off-by: Carson Cook <carson.cook@ibm.com> * Clean code smells Signed-off-by: Carson Cook <carson.cook@ibm.com> * Remove duplicate WebSecurity config Signed-off-by: Carson Cook <carson.cook@ibm.com> * Add integration tests for cert and basic auth Signed-off-by: Carson Cook <carson.cook@ibm.com> * Reset ssl config for each integration test Signed-off-by: Carson Cook <carson.cook@ibm.com> * fix docker config for catalog Signed-off-by: jandadav <janda.david@gmail.com> Co-authored-by: jandadav <janda.david@gmail.com>
1 parent 32d1443 commit 79dedfd

File tree

4 files changed

+227
-55
lines changed

4 files changed

+227
-55
lines changed

api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/security/SecurityConfiguration.java

Lines changed: 110 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,25 @@
1111

1212
import com.fasterxml.jackson.databind.ObjectMapper;
1313
import lombok.RequiredArgsConstructor;
14+
import org.springframework.beans.factory.annotation.Value;
1415
import org.springframework.context.annotation.Bean;
1516
import org.springframework.context.annotation.Configuration;
17+
import org.springframework.core.annotation.Order;
1618
import org.springframework.http.HttpMethod;
19+
import org.springframework.security.authentication.AuthenticationManager;
1720
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
1821
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1922
import org.springframework.security.config.annotation.web.builders.WebSecurity;
2023
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
2124
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
2225
import org.springframework.security.config.http.SessionCreationPolicy;
26+
import org.springframework.security.core.userdetails.User;
27+
import org.springframework.security.core.userdetails.UserDetailsService;
2328
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
2429
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
30+
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
2531
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
32+
import org.zowe.apiml.product.filter.AttlsFilter;
2633
import org.zowe.apiml.security.client.EnableApimlAuth;
2734
import org.zowe.apiml.security.client.login.GatewayLoginProvider;
2835
import org.zowe.apiml.security.client.token.GatewayTokenProvider;
@@ -33,6 +40,8 @@
3340
import org.zowe.apiml.security.common.login.LoginFilter;
3441
import org.zowe.apiml.security.common.login.ShouldBeAlreadyAuthenticatedFilter;
3542

43+
import java.util.Collections;
44+
3645
/**
3746
* Main configuration class of Spring web security for Api Catalog
3847
* binds authentication managers
@@ -44,35 +53,100 @@
4453
@EnableWebSecurity
4554
@RequiredArgsConstructor
4655
@EnableApimlAuth
47-
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
56+
public class SecurityConfiguration {
57+
private static final String APIDOC_ROUTES = "/apidoc/**";
4858

4959
private final ObjectMapper securityObjectMapper;
5060
private final AuthConfigurationProperties authConfigurationProperties;
5161
private final HandlerInitializer handlerInitializer;
5262
private final GatewayLoginProvider gatewayLoginProvider;
5363
private final GatewayTokenProvider gatewayTokenProvider;
5464

55-
@Override
56-
protected void configure(AuthenticationManagerBuilder auth) {
57-
auth.authenticationProvider(gatewayLoginProvider);
58-
auth.authenticationProvider(gatewayTokenProvider);
65+
/**
66+
* Filter chain for protecting /apidoc/** endpoints with MF credentials for client certificate.
67+
*/
68+
@Configuration
69+
@Order(1)
70+
public class FilterChainBasicAuthOrTokenOrCertForApiDoc extends WebSecurityConfigurerAdapter {
71+
72+
@Value("${apiml.security.ssl.verifySslCertificatesOfServices:true}")
73+
private boolean verifySslCertificatesOfServices;
74+
75+
@Value("${apiml.security.ssl.nonStrictVerifySslCertificatesOfServices:false}")
76+
private boolean nonStrictVerifySslCertificatesOfServices;
77+
78+
@Value("${server.attls.enabled:false}")
79+
private boolean isAttlsEnabled;
80+
81+
@Override
82+
protected void configure(AuthenticationManagerBuilder auth) {
83+
auth.authenticationProvider(gatewayLoginProvider);
84+
auth.authenticationProvider(gatewayTokenProvider);
85+
}
86+
87+
@Override
88+
protected void configure(HttpSecurity http) throws Exception {
89+
mainframeCredentialsConfiguration(
90+
baseConfiguration(http.antMatcher(APIDOC_ROUTES)),
91+
authenticationManager()
92+
)
93+
.authorizeRequests()
94+
.antMatchers(APIDOC_ROUTES).authenticated();
95+
96+
if (verifySslCertificatesOfServices || nonStrictVerifySslCertificatesOfServices) {
97+
http.x509().userDetailsService(x509UserDetailsService());
98+
if (isAttlsEnabled) {
99+
http.addFilterBefore(new AttlsFilter(), X509AuthenticationFilter.class);
100+
}
101+
}
102+
}
103+
104+
private UserDetailsService x509UserDetailsService() {
105+
return username -> new User(username, "", Collections.emptyList());
106+
}
59107
}
60108

61-
@Override
62-
public void configure(WebSecurity web) {
63-
// skip security filters matchers
64-
String[] noSecurityAntMatchers = {
65-
"/",
66-
"/static/**",
67-
"/favicon.ico",
68-
"/api-doc"
69-
};
70-
71-
web.ignoring().antMatchers(noSecurityAntMatchers);
109+
/**
110+
* Default filter chain to protect all routes with MF credentials.
111+
*/
112+
@Configuration
113+
public class FilterChainBasicAuthOrTokenAllEndpoints extends WebSecurityConfigurerAdapter {
114+
115+
@Override
116+
protected void configure(AuthenticationManagerBuilder auth) {
117+
auth.authenticationProvider(gatewayLoginProvider);
118+
auth.authenticationProvider(gatewayTokenProvider);
119+
}
120+
121+
@Override
122+
public void configure(WebSecurity web) {
123+
// skip security filters matchers
124+
String[] noSecurityAntMatchers = {
125+
"/",
126+
"/static/**",
127+
"/favicon.ico",
128+
"/api-doc"
129+
};
130+
131+
web.ignoring().antMatchers(noSecurityAntMatchers);
132+
}
133+
134+
@Override
135+
protected void configure(HttpSecurity http) throws Exception {
136+
mainframeCredentialsConfiguration(
137+
baseConfiguration(http),
138+
authenticationManager()
139+
)
140+
.authorizeRequests()
141+
.antMatchers("/static-api/**").authenticated()
142+
.antMatchers("/containers/**").authenticated()
143+
.antMatchers(APIDOC_ROUTES).authenticated()
144+
.antMatchers("/application/health", "/application/info").permitAll()
145+
.antMatchers("/application/**").authenticated();
146+
}
72147
}
73148

74-
@Override
75-
protected void configure(HttpSecurity http) throws Exception {
149+
private HttpSecurity baseConfiguration(HttpSecurity http) throws Exception {
76150
http
77151
.csrf().disable() // NOSONAR
78152
.headers()
@@ -85,20 +159,24 @@ protected void configure(HttpSecurity http) throws Exception {
85159
handlerInitializer.getBasicAuthUnauthorizedHandler(), new AntPathRequestMatcher("/application/**")
86160
)
87161
.defaultAuthenticationEntryPointFor(
88-
handlerInitializer.getBasicAuthUnauthorizedHandler(), new AntPathRequestMatcher("/apidoc/**")
162+
handlerInitializer.getBasicAuthUnauthorizedHandler(), new AntPathRequestMatcher(APIDOC_ROUTES)
89163
)
90164
.defaultAuthenticationEntryPointFor(
91165
handlerInitializer.getUnAuthorizedHandler(), new AntPathRequestMatcher("/**")
92166
)
93167

94168
.and()
95169
.sessionManagement()
96-
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
170+
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
97171

172+
return http;
173+
}
174+
175+
private HttpSecurity mainframeCredentialsConfiguration(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
176+
http
98177
// login endpoint
99-
.and()
100178
.addFilterBefore(new ShouldBeAlreadyAuthenticatedFilter(authConfigurationProperties.getServiceLoginEndpoint(), handlerInitializer.getAuthenticationFailureHandler()), UsernamePasswordAuthenticationFilter.class)
101-
.addFilterBefore(loginFilter(authConfigurationProperties.getServiceLoginEndpoint()), ShouldBeAlreadyAuthenticatedFilter.class)
179+
.addFilterBefore(loginFilter(authConfigurationProperties.getServiceLoginEndpoint(), authenticationManager), ShouldBeAlreadyAuthenticatedFilter.class)
102180
.authorizeRequests()
103181
.antMatchers(HttpMethod.POST, authConfigurationProperties.getServiceLoginEndpoint()).permitAll()
104182

@@ -110,33 +188,29 @@ protected void configure(HttpSecurity http) throws Exception {
110188

111189
// endpoints protection
112190
.and()
113-
.addFilterBefore(basicFilter(), UsernamePasswordAuthenticationFilter.class)
114-
.addFilterBefore(cookieFilter(), UsernamePasswordAuthenticationFilter.class)
115-
.authorizeRequests()
116-
.antMatchers("/static-api/**").authenticated()
117-
.antMatchers("/containers/**").authenticated()
118-
.antMatchers("/apidoc/**").authenticated()
119-
.antMatchers("/application/health", "/application/info").permitAll()
120-
.antMatchers("/application/**").authenticated();
191+
.addFilterBefore(basicFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
192+
.addFilterBefore(cookieFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);
193+
194+
return http;
121195
}
122196

123-
private LoginFilter loginFilter(String loginEndpoint) throws Exception {
197+
private LoginFilter loginFilter(String loginEndpoint, AuthenticationManager authenticationManager) {
124198
return new LoginFilter(
125199
loginEndpoint,
126200
handlerInitializer.getSuccessfulLoginHandler(),
127201
handlerInitializer.getAuthenticationFailureHandler(),
128202
securityObjectMapper,
129-
authenticationManager(),
203+
authenticationManager,
130204
handlerInitializer.getResourceAccessExceptionHandler()
131205
);
132206
}
133207

134208
/**
135209
* Secures content with a basic authentication
136210
*/
137-
private BasicContentFilter basicFilter() throws Exception {
211+
private BasicContentFilter basicFilter(AuthenticationManager authenticationManager) {
138212
return new BasicContentFilter(
139-
authenticationManager(),
213+
authenticationManager,
140214
handlerInitializer.getAuthenticationFailureHandler(),
141215
handlerInitializer.getResourceAccessExceptionHandler()
142216
);
@@ -145,9 +219,9 @@ private BasicContentFilter basicFilter() throws Exception {
145219
/**
146220
* Secures content with a token stored in a cookie
147221
*/
148-
private CookieContentFilter cookieFilter() throws Exception {
222+
private CookieContentFilter cookieFilter(AuthenticationManager authenticationManager) {
149223
return new CookieContentFilter(
150-
authenticationManager(),
224+
authenticationManager,
151225
handlerInitializer.getAuthenticationFailureHandler(),
152226
handlerInitializer.getResourceAccessExceptionHandler(),
153227
authConfigurationProperties);

api-catalog-services/src/main/resources/application.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ server:
9191
trustStore: ${apiml.security.ssl.trustStore}
9292
trustStoreType: ${apiml.security.ssl.trustStoreType}
9393
trustStorePassword: ${apiml.security.ssl.trustStorePassword}
94+
clientAuth: want
9495
error:
9596
whitelabel:
9697
enabled: false

config/docker/api-catalog-services.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ server:
1616
keyStore: /docker/all-services.keystore.p12
1717
keyStorePassword: password
1818
keyStoreType: PKCS12
19-
clientAuth:
19+
clientAuth: want
2020

2121
apiml:
2222
service:

0 commit comments

Comments
 (0)