Skip to content
This repository has been archived by the owner on May 16, 2023. It is now read-only.

Commit

Permalink
* Add Allow-List-Check for mTLS Authentication (#246)
Browse files Browse the repository at this point in the history
* Add Allow-List-Check for mTLS Authentication

* Change Allow-List to Comma seperated

Co-authored-by: Felix Dittrich <[email protected]>
  • Loading branch information
mschulte-tsi and f11h authored Nov 19, 2021
1 parent ac33d9a commit e38924a
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,22 @@

package app.coronawarn.verification.config;

import java.util.Arrays;
import org.springframework.context.annotation.Bean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
protected HttpFirewall strictFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(Arrays.asList(
HttpMethod.GET.name(),
HttpMethod.POST.name(),
HttpMethod.HEAD.name()
));
return firewall;
}

@ConditionalOnProperty(name = "server.ssl.client-auth", havingValue = "none", matchIfMissing = true)
public class LocalSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/api/**").permitAll().and().requiresChannel().mvcMatchers("/api/**").requiresSecure().and()
.authorizeRequests()
.mvcMatchers("/version/**").permitAll()
.mvcMatchers("/actuator/**").permitAll()
.anyRequest().denyAll()
.anyRequest().permitAll()
.and().csrf().disable();
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Corona-Warn-App / cwa-verification
*
* (C) 2020, T-Systems International GmbH
*
* Deutsche Telekom AG and all other contributors /
* copyright owners license this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package app.coronawarn.verification.config;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.web.server.ResponseStatusException;

@Configuration
@Slf4j
@RequiredArgsConstructor
@ConditionalOnProperty(name = "server.ssl.client-auth", havingValue = "need")
public class MtlsSecurityConfig extends WebSecurityConfigurerAdapter {

private final VerificationApplicationConfig config;

@Bean
protected HttpFirewall strictFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(Arrays.asList(
HttpMethod.GET.name(),
HttpMethod.POST.name()
));
return firewall;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/api/**").authenticated().and()
.requiresChannel().mvcMatchers("/api/**").requiresSecure().and()
.x509().x509PrincipalExtractor(new ThumbprintX509PrincipalExtractor()).userDetailsService(userDetailsService())
.and().authorizeRequests()
.mvcMatchers("/version/**").permitAll()
.mvcMatchers("/actuator/**").permitAll()
.anyRequest().denyAll()
.and().csrf().disable();
}

@Override
public UserDetailsService userDetailsService() {
return hash -> {

boolean allowed = Stream.of(config.getAllowedClientCertificates()
.split(","))
.map(String::trim)
.anyMatch(entry -> entry.equalsIgnoreCase(hash));

if (allowed) {
return new User(hash, "", Collections.emptyList());
} else {
log.error("Failed to authenticate cert with hash {}", hash);
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
};
}

private static class ThumbprintX509PrincipalExtractor implements X509PrincipalExtractor {

MessageDigest messageDigest;

private ThumbprintX509PrincipalExtractor() throws NoSuchAlgorithmException {
messageDigest = MessageDigest.getInstance("SHA-256");
}

@Override
public Object extractPrincipal(X509Certificate x509Certificate) {
try {
return String.valueOf(Hex.encode(messageDigest.digest(x509Certificate.getEncoded())));
} catch (CertificateEncodingException e) {
log.error("Failed to extract bytes from certificate");
return null;
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class VerificationApplicationConfig {
private Long initialFakeDelayMilliseconds;

private Long fakeDelayMovingAverageSamples;
private String allowedClientCertificates;

private Tan tan = new Tan();
private AppSession appsession = new AppSession();
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/application-cloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ cwa-testresult-server:
trust-store: ${SSL_VERIFICATION_TRUSTSTORE_PATH}
trust-store-password: ${SSL_VERIFICATION_TRUSTSTORE_PASSWORD}
disable-dob-hash-check-for-external-test-result: ${DISABLE_DOB_EXTERNAL_TR}
allowed-client-certificates: ${VERIFICATION_ALLOWEDCLIENTCERTIFICATES}
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,4 @@ request:

cwa-testresult-server:
url: http://localhost:8088
allowed-client-certificates:

0 comments on commit e38924a

Please sign in to comment.