diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AADSecurityConfig.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AADSecurityConfig.java index 74294bef5653362c8db58e6e5c5fea46d00f833f..498e85ae3cb47f13507326d51250142ee405a6d5 100644 --- a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AADSecurityConfig.java +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AADSecurityConfig.java @@ -16,6 +16,7 @@ package org.opengroup.osdu.partition.provider.azure.security; import com.azure.spring.autoconfigure.aad.AADAppRoleStatelessAuthenticationFilter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -25,8 +26,10 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +// TODO: check if this class can be safely deleted. @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) +@ConditionalOnProperty(value = "azure.istio.auth.enabled", havingValue = "false", matchIfMissing = false) public class AADSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AzureIstioSecurityConfig.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AzureIstioSecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..d1141dbfc4fdd2f691aba8997f008795d0b13946 --- /dev/null +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AzureIstioSecurityConfig.java @@ -0,0 +1,24 @@ +package org.opengroup.osdu.partition.provider.azure.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +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.authentication.UsernamePasswordAuthenticationFilter; + +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +@ConditionalOnProperty(value = "azure.istio.auth.enabled", havingValue = "true", matchIfMissing = true) +public class AzureIstioSecurityConfig extends WebSecurityConfigurerAdapter { + @Autowired + private AzureIstioSecurityFilter appRoleAuthFilter; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.httpBasic().disable() + .csrf().disable() + .addFilterBefore(appRoleAuthFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AzureIstioSecurityFilter.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AzureIstioSecurityFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..b079cd6344130047a5e7efe5c5b3923cd1cc0c90 --- /dev/null +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AzureIstioSecurityFilter.java @@ -0,0 +1,98 @@ +package org.opengroup.osdu.partition.provider.azure.security; + +import com.azure.spring.autoconfigure.aad.UserPrincipal; +import com.nimbusds.jwt.JWTClaimsSet; +import net.minidev.json.JSONArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.text.ParseException; +import java.util.*; +import java.util.stream.Collectors; + +import static org.springframework.util.StringUtils.hasText; + +@Component +@ConditionalOnProperty(value = "azure.istio.auth.enabled", havingValue = "true", matchIfMissing = true) +public class AzureIstioSecurityFilter extends OncePerRequestFilter { + private static final Logger LOGGER = LoggerFactory.getLogger(AzureIstioSecurityFilter.class); + + private static final String X_ISTIO_CLAIMS_PAYLOAD = "x-payload"; + private static final JSONArray DEFAULT_ROLE_CLAIM = new JSONArray().appendElement("USER"); + private static final String ROLE_PREFIX = "ROLE_"; + + /** + * Filter logic. + * @param servletRequest Request object. + * @param servletResponse Response object. + * @param filterChain Filter Chain object. + * @throws IOException + * @throws ServletException + */ + @Override + protected void doFilterInternal(final HttpServletRequest servletRequest, final HttpServletResponse servletResponse, final FilterChain filterChain) throws ServletException, IOException { + final String istioPayload = servletRequest.getHeader(X_ISTIO_CLAIMS_PAYLOAD); + + LOGGER.info(String.format("Received headers list: %s", Collections.list(servletRequest.getHeaderNames()))); + + try { + if (hasText(istioPayload)) { + JWTClaimsSet claimsSet = JWTClaimsSet.parse(new String(Base64.getDecoder().decode(istioPayload))); + + final JSONArray roles = Optional.ofNullable((JSONArray) claimsSet.getClaims().get("roles")) + .filter(r -> !r.isEmpty()) + .orElse(DEFAULT_ROLE_CLAIM); + + // By default the authenticated is set to true as part PreAuthenticatedAuthenticationToken constructor. + // TODO: check where the claims were being set in case of ADSecurityConfig + SecurityContextHolder + .getContext() + .setAuthentication( + // TODO: check if aadIssuedBearerToken parameter should be null here + new PreAuthenticatedAuthenticationToken( + new UserPrincipal(null,null, claimsSet), + null, + rolesToGrantedAuthorities(roles) + )); + } else { + SecurityContextHolder + .getContext() + .setAuthentication( + new PreAuthenticatedAuthenticationToken( + null, null, null + )); + } + } catch (ParseException ex) { + LOGGER.error("Failed to initialize UserPrincipal.", ex); + throw new ServletException(ex); + } + try { + filterChain.doFilter(servletRequest, servletResponse); + } finally { + SecurityContextHolder.clearContext(); + } + } + + /** + * To return roles. + * @param roles Request Object. + * @return set representation of roles. + */ + protected Set<SimpleGrantedAuthority> rolesToGrantedAuthorities(final JSONArray roles) { + return roles.stream() + .filter(Objects::nonNull) + .map(s -> new SimpleGrantedAuthority(ROLE_PREFIX + s)) + .collect(Collectors.toSet()); + } +} diff --git a/provider/partition-azure/src/main/resources/application.properties b/provider/partition-azure/src/main/resources/application.properties index 7da879f59be16d728b121d0226f59f795cc03381..a047bd97f1b3dff339a792dec4b49e9078f148dc 100644 --- a/provider/partition-azure/src/main/resources/application.properties +++ b/provider/partition-azure/src/main/resources/application.properties @@ -16,6 +16,9 @@ azure.activedirectory.client-id=${aad_client_id} azure.activedirectory.AppIdUri=api://${azure.activedirectory.client-id} azure.activedirectory.session-stateless=true +# Istio +azure.istio.auth.enabled=${azure_istioauth_enabled} + # Azure KeyVault configuration azure.keyvault.url=${KEYVAULT_URI} diff --git a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/security/AADSecurityConfigTest.java b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/security/AADSecurityConfigTest.java index 537c7c78f214a9a4b66949d7764fb730f8896dd0..35a273958aa4336a6a7d5853fee3ae9ae3303efc 100644 --- a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/security/AADSecurityConfigTest.java +++ b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/security/AADSecurityConfigTest.java @@ -34,7 +34,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) -@SpringBootTest(classes = {HomeController.class, +@SpringBootTest(properties = {"azure.istio.auth.enabled=true"}, classes = {HomeController.class, PartitionApi.class, AADSecurityConfig.class, AADAppRoleStatelessAuthenticationFilter.class}) diff --git a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/security/WhoamiControllerTest.java b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/security/WhoamiControllerTest.java index 2ca8d7cf63640124a4f3c28de8cd8ae5935f39e1..e074adf93b45cfaa0c4c73d47f194a62ae4593a6 100644 --- a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/security/WhoamiControllerTest.java +++ b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/security/WhoamiControllerTest.java @@ -39,7 +39,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @RunWith(SpringRunner.class) @PrepareForTest(SecurityContextHolder.class) -@SpringBootTest(classes = {WhoamiController.class, AADSecurityConfig.class, +@SpringBootTest(properties = {"azure.istio.auth.enabled=true"}, classes = {WhoamiController.class, AADSecurityConfig.class, AADAppRoleStatelessAuthenticationFilter.class}) @WebAppConfiguration public class WhoamiControllerTest {