diff --git a/NOTICE b/NOTICE index 93149c8b0503bf9b04924064da9805957ce90e21..95e0dcfe686064ed81e1a99e20afe0e09a047809 100644 --- a/NOTICE +++ b/NOTICE @@ -17,7 +17,7 @@ The following software have components provided under the terms of this license: - Apache Commons CLI (from http://commons.apache.org/proper/commons-cli/) - Cobertura (from http://cobertura.sourceforge.net) - Default Plexus Container (from https://repo1.maven.org/maven2/org/codehaus/plexus/plexus-container-default) -- Plexus Common Utilities (from http://plexus.codehaus.org/plexus-utils) +- Plexus Common Utilities (from https://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils) - oro (from ) ======================================================================== @@ -380,14 +380,14 @@ The following software have components provided under the terms of this license: - Jackson datatype: Joda (from https://github.com/FasterXML/jackson-datatype-joda) - Jackson datatype: jdk8 (from https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8) - Jackson extensions to the Google HTTP Client Library for Java. (from https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson) -- Jackson module: JAXB Annotations (from https://github.com/FasterXML/jackson-modules-base) +- Jackson module: Afterburner (from https://github.com/FasterXML/jackson-modules-base) +- Jackson module: Old JAXB Annotations (javax.xml.bind) (from https://github.com/FasterXML/jackson-modules-base) - Jackson-annotations (from http://github.com/FasterXML/jackson) - Jackson-annotations (from http://github.com/FasterXML/jackson) - Jackson-core (from https://github.com/FasterXML/jackson-core) - Jackson-core (from https://github.com/FasterXML/jackson-core) - Jackson-dataformat-XML (from https://github.com/FasterXML/jackson-dataformat-xml) - Jackson-dataformat-YAML (from https://github.com/FasterXML/jackson-dataformats-text) -- Jackson-module-Afterburner (from http://wiki.fasterxml.com/JacksonHome) - Jackson-module-parameter-names (from https://repo1.maven.org/maven2/com/fasterxml/jackson/module/jackson-module-parameter-names) - Jakarta Bean Validation API (from https://beanvalidation.org) - Jakarta Expression Language Implementation (from https://projects.eclipse.org/projects/ee4j.el) @@ -483,7 +483,7 @@ The following software have components provided under the terms of this license: - OpenCensus (from https://github.com/census-instrumentation/opencensus-java) - OpenCensus (from https://github.com/census-instrumentation/opencensus-java) - PWDB :: Database (from https://repo1.maven.org/maven2/org/linguafranca/pwdb/database) -- Plexus Common Utilities (from http://plexus.codehaus.org/plexus-utils) +- Plexus Common Utilities (from https://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils) - Plexus Velocity Component (from ) - PowerMock (from http://www.powermock.org) - PowerMock (from http://www.powermock.org) @@ -630,7 +630,7 @@ The following software have components provided under the terms of this license: - Hamcrest (from http://hamcrest.org/JavaHamcrest/) - Hamcrest Core (from http://hamcrest.org/) - HdrHistogram (from http://hdrhistogram.github.io/HdrHistogram/) -- Plexus Common Utilities (from http://plexus.codehaus.org/plexus-utils) +- Plexus Common Utilities (from https://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils) - Reflections (from http://github.com/ronmamo/reflections) - Stax2 API (from http://github.com/FasterXML/stax2-api) - ThreeTen backport (from https://www.threeten.org/threetenbp) @@ -664,7 +664,7 @@ The following software have components provided under the terms of this license: - Microsoft Application Insights Java SDK Web Module (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Log4j 2 Appender (from https://github.com/Microsoft/ApplicationInsights-Java) - Netty/Codec/HTTP (from https://repo1.maven.org/maven2/io/netty/netty-codec-http) -- Plexus Common Utilities (from http://plexus.codehaus.org/plexus-utils) +- Plexus Common Utilities (from https://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils) - Protocol Buffers [Core] (from https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java) - Protocol Buffers [Util] (from https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util) - Reflections (from http://github.com/ronmamo/reflections) @@ -965,7 +965,7 @@ Public-Domain ======================================================================== The following software have components provided under the terms of this license: -- Plexus Common Utilities (from http://plexus.codehaus.org/plexus-utils) +- Plexus Common Utilities (from https://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils) - Spongy Castle (from http://rtyley.github.io/spongycastle/) ======================================================================== diff --git a/devops/azure/chart/templates/deployment.yaml b/devops/azure/chart/templates/deployment.yaml index 41f3b2a47e175ed112d280b5d905d4fb6f0dee87..da322c003bc23ed68055050252d314d7ffc7537d 100644 --- a/devops/azure/chart/templates/deployment.yaml +++ b/devops/azure/chart/templates/deployment.yaml @@ -94,3 +94,5 @@ spec: value: "api://$(aad_client_id)" - name: azure_activedirectory_session_stateless value: "true" + - name: azure_istioauth_enabled + value: "true" \ No newline at end of file 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..036ffac11919750cee86d71bd32a1e7b5f9448a8 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; @@ -27,6 +28,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic @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..63bd1ac21a87d26c47494ce120a035df6178830a --- /dev/null +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/security/AzureIstioSecurityFilter.java @@ -0,0 +1,97 @@ +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.opengroup.osdu.core.common.model.http.AppException; +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.debug("Received headers list: {}", 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. + SecurityContextHolder + .getContext() + .setAuthentication( + 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 AppException(500, "Unable to parse claims in istio payload", ex.getMessage()); + } + 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/java/org/opengroup/osdu/partition/provider/azure/utils/AuthorizationService.java b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/utils/AuthorizationService.java index 8bddf4abad03da90b805c997690ea9f6e8bfd39f..ed0f6df00daec6a4a34da62a3a95eb4ca09c2a88 100644 --- a/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/utils/AuthorizationService.java +++ b/provider/partition-azure/src/main/java/org/opengroup/osdu/partition/provider/azure/utils/AuthorizationService.java @@ -25,6 +25,9 @@ import java.util.Map; @Component public class AuthorizationService implements IAuthorizationService { + private final String AAD_issuer_v1 = "https://sts.windows.net"; + private final String AAD_issuer_v2 = "https://login.microsoftonline.com"; + enum UserType { REGULAR_USER, GUEST_USER, @@ -40,14 +43,24 @@ public class AuthorizationService implements IAuthorizationService { } final UserPrincipal userPrincipal = (UserPrincipal) principal; + String issuer = userPrincipal.getClaim("iss").toString(); UserType type = getType(userPrincipal); - if (type == UserType.SERVICE_PRINCIPAL) { + if (type == UserType.SERVICE_PRINCIPAL && issuedByAAD(issuer)) { return true; } return false; } + /*** + * Check that issuer string startswith accepted prefix of AAD issuer url (V1 or V2). + * @param issuer claim for "issuer" + * @return true if issuer startswith V1 url or V2 url + */ + private boolean issuedByAAD(String issuer) { + return issuer.startsWith(AAD_issuer_v1) || issuer.startsWith(AAD_issuer_v2); + } + /** * The internal method to get the user principal. * diff --git a/provider/partition-azure/src/main/resources/application-local.properties b/provider/partition-azure/src/main/resources/application-local.properties new file mode 100644 index 0000000000000000000000000000000000000000..f4e0b0a2b5ff29de00ec8eb23ccbc8e8f25f9cff --- /dev/null +++ b/provider/partition-azure/src/main/resources/application-local.properties @@ -0,0 +1,22 @@ +# Copyright © Microsoft Corporation +# +# Licensed 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. + +runtime.env.local=true + +azure.activedirectory.client-id=${aad_client_id} +azure.activedirectory.AppIdUri=api://${azure.activedirectory.client-id} +azure.activedirectory.session-stateless=true + +# Istio Auth Enabled +azure.istio.auth.enabled=false diff --git a/provider/partition-azure/src/main/resources/application.properties b/provider/partition-azure/src/main/resources/application.properties index 7da879f59be16d728b121d0226f59f795cc03381..cb27e04fa68708d72743eb3305bdae2a39943311 100644 --- a/provider/partition-azure/src/main/resources/application.properties +++ b/provider/partition-azure/src/main/resources/application.properties @@ -11,10 +11,16 @@ logging.slf4jlogger.enabled=true logging.mdccontext.enabled=true # AAD properties +# Azure AD configuration, commented below settings to disable AAD AuthN, +# Uncomment it in the Istio AUTHN disabled Scenario +#azure.activedirectory.AppIdUri=api://${azure.activedirectory.client-id} +#azure.activedirectory.session-stateless=true + azure.activedirectory.app-resource-id=${aad_client_id} 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..993c19a3c8362c998c46d9faf12386244188923b 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=false"}, 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..e3450cbd2871ee59ed70358d1ed2d8ea506a6275 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=false"}, classes = {WhoamiController.class, AADSecurityConfig.class, AADAppRoleStatelessAuthenticationFilter.class}) @WebAppConfiguration public class WhoamiControllerTest { diff --git a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/utils/AuthorizationServiceTest.java b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/utils/AuthorizationServiceTest.java index f76d96dcc47e2e8f0f8e37fe858ba768a79e88bc..0d732a671c334790714808b69fc07b0ca95e23ba 100644 --- a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/utils/AuthorizationServiceTest.java +++ b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/utils/AuthorizationServiceTest.java @@ -103,11 +103,23 @@ public class AuthorizationServiceTest { } @Test - public void shouldReturnTrueWhenAADTokenIsSetInContext() { + public void shouldReturnTrueWhenAADTokenIsSetInContext_AndIssuerIsAAD() { createAADUserPrincipalSetSecurityContext(TestUtils.APPID, TestUtils.getAppId(), TestUtils.getAadIssuer()); assertTrue(authorizationService.isDomainAdminServiceAccount()); } + @Test + public void shouldReturnTrueWhenAADTokenIsSetInContext_AndIssuerIsAADV2() { + createAADUserPrincipalSetSecurityContext(TestUtils.APPID, TestUtils.getAppId(), TestUtils.getAadIssuerV2()); + assertTrue(authorizationService.isDomainAdminServiceAccount()); + } + + @Test + public void shouldReturnFalseWhenAADTokenIsSetInContext_AndIssuerIsNotAAD() { + createAADUserPrincipalSetSecurityContext(TestUtils.APPID, TestUtils.getAppId(), TestUtils.getNonAadIssuer()); + assertFalse(authorizationService.isDomainAdminServiceAccount()); + } + @Getter public class DummyAuthToken { diff --git a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/utils/TestUtils.java b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/utils/TestUtils.java index 392665322ef141e5fd31a07da30529980b77ae0a..cfa415a48ae7de425f8b9dd58705baea92260628 100644 --- a/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/utils/TestUtils.java +++ b/provider/partition-azure/src/test/java/org/opengroup/osdu/partition/provider/azure/utils/TestUtils.java @@ -18,7 +18,11 @@ public class TestUtils { private static final String appId = "1234"; public static final String APPID = "appid"; public static final String aadIssuer = "https://sts.windows.net"; + public static final String aadIssuerV2 = "https://login.microsoftonline.com"; + public static final String nonAadIssuer = "https://login.abc.com"; public static String getAppId() {return appId;} public static String getAadIssuer() {return aadIssuer;} + public static String getAadIssuerV2() {return aadIssuerV2;} + public static String getNonAadIssuer() {return nonAadIssuer;} }