adding XACML based authorization for API calls.
Project: http://git-wip-us.apache.org/repos/asf/airavata/repo Commit: http://git-wip-us.apache.org/repos/asf/airavata/commit/9c02f24d Tree: http://git-wip-us.apache.org/repos/asf/airavata/tree/9c02f24d Diff: http://git-wip-us.apache.org/repos/asf/airavata/diff/9c02f24d Branch: refs/heads/master Commit: 9c02f24d99c139b7dcc38b6fcddd17dd935c8e73 Parents: 7ef8368 Author: hasinitg <[email protected]> Authored: Sat Aug 1 01:19:34 2015 +0530 Committer: hasinitg <[email protected]> Committed: Sat Aug 1 01:19:34 2015 +0530 ---------------------------------------------------------------------- airavata-api/airavata-api-server/pom.xml | 8 +- .../security/AiravataSecurityManager.java | 4 +- .../DefaultAiravataSecurityManager.java | 21 ++++- .../api/server/security/DefaultOAuthClient.java | 55 +++--------- .../api/server/security/DefaultXACMLPEP.java | 92 ++++++++++++++++++++ .../server/security/SecurityInterceptor.java | 17 +++- .../apache/airavata/common/utils/Constants.java | 6 ++ .../resources/airavata-default-xacml-policy.xml | 62 +++++++++++++ 8 files changed, 211 insertions(+), 54 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/airavata/blob/9c02f24d/airavata-api/airavata-api-server/pom.xml ---------------------------------------------------------------------- diff --git a/airavata-api/airavata-api-server/pom.xml b/airavata-api/airavata-api-server/pom.xml index 7cd0f3b..543bbaa 100644 --- a/airavata-api/airavata-api-server/pom.xml +++ b/airavata-api/airavata-api-server/pom.xml @@ -8,7 +8,8 @@ ANY ~ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> @@ -107,6 +108,11 @@ <version>4.2.0</version> </dependency> <dependency> + <groupId>org.wso2.carbon</groupId> + <artifactId>org.wso2.carbon.identity.entitlement.stub</artifactId> + <version>4.2.1</version> + </dependency> + <dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>4.0</version> http://git-wip-us.apache.org/repos/asf/airavata/blob/9c02f24d/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java ---------------------------------------------------------------------- diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java index 348675f..37c348c 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/AiravataSecurityManager.java @@ -23,6 +23,8 @@ package org.apache.airavata.api.server.security; import org.apache.airavata.model.security.AuthzToken; import org.apache.airavata.security.AiravataSecurityException; +import java.util.Map; + public interface AiravataSecurityManager { - public boolean isUserAuthorized(AuthzToken authzToken) throws AiravataSecurityException; + public boolean isUserAuthorized(AuthzToken authzToken, Map<String, String> metaData) throws AiravataSecurityException; } http://git-wip-us.apache.org/repos/asf/airavata/blob/9c02f24d/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java ---------------------------------------------------------------------- diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java index 9d7c959..6230310 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultAiravataSecurityManager.java @@ -24,6 +24,7 @@ import org.apache.airavata.common.exception.ApplicationSettingsException; import org.apache.airavata.common.utils.ServerSettings; import org.apache.airavata.model.security.AuthzToken; import org.apache.airavata.security.AiravataSecurityException; +import org.apache.airavata.security.util.TrustStoreManager; import org.apache.axis2.AxisFault; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.ConfigurationContextFactory; @@ -31,22 +32,36 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationResponseDTO; +import java.util.Map; + /** * This enforces authentication and authorization on Airavata API calls. */ public class DefaultAiravataSecurityManager implements AiravataSecurityManager { private final static Logger logger = LoggerFactory.getLogger(DefaultAiravataSecurityManager.class); - public boolean isUserAuthorized(AuthzToken authzToken) throws AiravataSecurityException { + public boolean isUserAuthorized(AuthzToken authzToken, Map<String, String> metaData) throws AiravataSecurityException { try { ConfigurationContext configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null); - //TODO:read following properties from server-settings.properties file. + + //initialize SSL context with the trust store that contains the public cert of WSO2 Identity Server. + TrustStoreManager trustStoreManager = new TrustStoreManager(); + trustStoreManager.initializeTrustStoreManager(ServerSettings.getTrustStorePath(), + ServerSettings.getTrustStorePassword()); + DefaultOAuthClient oauthClient = new DefaultOAuthClient(ServerSettings.getRemoteOauthServerUrl(), ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); OAuth2TokenValidationResponseDTO validationResponse = oauthClient.validateAccessToken( authzToken.getAccessToken()); - return validationResponse.getValid(); + boolean isOAuthTokenValid = validationResponse.getValid(); + //if XACML based authorization is enabled, check for role based authorization for the API invocation + DefaultXACMLPEP entitlementClient = new DefaultXACMLPEP(ServerSettings.getRemoteOauthServerUrl(), + ServerSettings.getAdminUsername(), ServerSettings.getAdminPassword(), configContext); + boolean authorizationDecision = entitlementClient.getAuthorizationDecision(authzToken, metaData); + + return (isOAuthTokenValid && authorizationDecision); + } catch (AxisFault axisFault) { logger.error(axisFault.getMessage(), axisFault); throw new AiravataSecurityException("Error in initializing the configuration context for creating the OAuth validation client."); http://git-wip-us.apache.org/repos/asf/airavata/blob/9c02f24d/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java ---------------------------------------------------------------------- diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java index 7996474..e1afacd 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultOAuthClient.java @@ -58,47 +58,14 @@ public class DefaultOAuthClient { */ public DefaultOAuthClient(String auhorizationServerURL, String username, String password, ConfigurationContext configCtx) throws AiravataSecurityException { - String serviceURL = auhorizationServerURL + "OAuth2TokenValidationService"; try { + String serviceURL = auhorizationServerURL + "OAuth2TokenValidationService"; stub = new OAuth2TokenValidationServiceStub(configCtx, serviceURL); CarbonUtils.setBasicAccessSecurityHeaders(username, password, true, stub._getServiceClient()); } catch (AxisFault e) { logger.error(e.getMessage(), e); throw new AiravataSecurityException("Error initializing OAuth client."); } - /*//TODO:Import the WSO2 IS cert into Airavata trust store. - try { - // Get SSL context - SSLContext sc = SSLContext.getInstance("SSL"); - - // Create empty HostnameVerifier - HostnameVerifier hv = new HostnameVerifier() { - public boolean verify(String urlHostName, SSLSession session) { - return true; - } - }; - HttpsURLConnection.setDefaultHostnameVerifier(hv); - - // Create a trust manager that does not validate certificate chains - TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; - } - - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, - String authType) { - } - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, - String authType) { - } - }}; - - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - SSLContext.setDefault(sc); - } catch (Exception e) { - e.printStackTrace(); - }*/ } /** @@ -110,24 +77,22 @@ public class DefaultOAuthClient { */ public OAuth2TokenValidationResponseDTO validateAccessToken(String accessToken) throws AiravataSecurityException { - OAuth2TokenValidationRequestDTO oauthReq = new OAuth2TokenValidationRequestDTO(); - OAuth2TokenValidationRequestDTO_OAuth2AccessToken token = - new OAuth2TokenValidationRequestDTO_OAuth2AccessToken(); - token.setIdentifier(accessToken); - token.setTokenType(BEARER_TOKEN_TYPE); - oauthReq.setAccessToken(token); + try { - //initialize SSL context with the trust store. - TrustStoreManager trustStoreManager = new TrustStoreManager(); - trustStoreManager.initializeTrustStoreManager(ServerSettings.getTrustStorePath(), ServerSettings.getTrustStorePassword()); + OAuth2TokenValidationRequestDTO oauthReq = new OAuth2TokenValidationRequestDTO(); + OAuth2TokenValidationRequestDTO_OAuth2AccessToken token = + new OAuth2TokenValidationRequestDTO_OAuth2AccessToken(); + token.setIdentifier(accessToken); + token.setTokenType(BEARER_TOKEN_TYPE); + oauthReq.setAccessToken(token); return stub.validate(oauthReq); } catch (RemoteException e) { logger.error(e.getMessage(), e); throw new AiravataSecurityException("Error in validating the OAuth access token."); - } catch (ApplicationSettingsException e) { + } /*catch (ApplicationSettingsException e) { logger.error(e.getMessage(), e); throw new AiravataSecurityException("Error in reading OAuth configuration."); - } + }*/ } } http://git-wip-us.apache.org/repos/asf/airavata/blob/9c02f24d/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java ---------------------------------------------------------------------- diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java new file mode 100644 index 0000000..371b35d --- /dev/null +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/DefaultXACMLPEP.java @@ -0,0 +1,92 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses 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 org.apache.airavata.api.server.security; + +import org.apache.airavata.common.utils.Constants; +import org.apache.airavata.model.security.AuthzToken; +import org.apache.airavata.security.AiravataSecurityException; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.ConfigurationContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceStub; +import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceException; +import org.wso2.carbon.utils.CarbonUtils; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.Map; + +/** + * This enforces XACML based fine grained authorization on the API calls. + */ +public class DefaultXACMLPEP { + + private final static Logger logger = LoggerFactory.getLogger(DefaultXACMLPEP.class); + private EntitlementServiceStub entitlementServiceStub; + + public DefaultXACMLPEP(String auhorizationServerURL, String username, String password, + ConfigurationContext configCtx) throws AiravataSecurityException { + try { + + String PDPURL = auhorizationServerURL + "EntitlementService"; + entitlementServiceStub = new EntitlementServiceStub(configCtx, PDPURL); + CarbonUtils.setBasicAccessSecurityHeaders(username, password, true, entitlementServiceStub._getServiceClient()); + } catch (AxisFault e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error initializing XACML PEP client."); + } + + } + + /** + * Send the XACML authorization request to XAML PDP and return the authorization decision. + * + * @param authzToken + * @param metaData + * @return + */ + public boolean getAuthorizationDecision(AuthzToken authzToken, Map<String, String> metaData) throws AiravataSecurityException { + String decision; + try { + String subject = authzToken.getClaimsMap().get(Constants.USER_NAME); + String action = "/airavata/" + metaData.get(Constants.API_METHOD_NAME); + String decisionString = entitlementServiceStub.getDecisionByAttributes(subject, null, action, null); + //parse the XML decision string and obtain the decision + + if ("NotApplicable".equals(decision) || "Indeterminate".equals(decision) || decision == null) { + logger.error("Authorization Decision is: " + decision); + throw new AiravataSecurityException("Error in authorizing the user."); + } + } catch (RemoteException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in authorizing the user."); + } catch (EntitlementServiceException e) { + logger.error(e.getMessage(), e); + throw new AiravataSecurityException("Error in authorizing the user."); + } + return Boolean.valueOf(decision); + } + + private String parseDecisionString(String decisionString) { + + } +} http://git-wip-us.apache.org/repos/asf/airavata/blob/9c02f24d/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java ---------------------------------------------------------------------- diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java index cf8f7e2..ff47e5a 100644 --- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java +++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/security/SecurityInterceptor.java @@ -19,9 +19,11 @@ * */ package org.apache.airavata.api.server.security; + import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.airavata.common.exception.ApplicationSettingsException; +import org.apache.airavata.common.utils.Constants; import org.apache.airavata.common.utils.ServerSettings; import org.apache.airavata.model.error.AuthorizationException; import org.apache.airavata.model.security.AuthzToken; @@ -29,17 +31,24 @@ import org.apache.airavata.security.AiravataSecurityException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; +import java.util.Map; + /** * Interceptor of Airavata API calls for the purpose of applying security. */ -public class SecurityInterceptor implements MethodInterceptor{ +public class SecurityInterceptor implements MethodInterceptor { private final static Logger logger = LoggerFactory.getLogger(SecurityInterceptor.class); + @Override public Object invoke(MethodInvocation invocation) throws Throwable { //obtain the authz token from the input parameters AuthzToken authzToken = (AuthzToken) invocation.getArguments()[0]; //authorize the API call - authorize(authzToken); + System.out.println("METHOD NAME: " + invocation.getMethod().getName()); + HashMap<String, String> metaDataMap = new HashMap(); + metaDataMap.put(Constants.API_METHOD_NAME, invocation.getMethod().getName()); + authorize(authzToken, metaDataMap); //set the user identity info in a thread local to be used in downstream execution. IdentityContext.set(authzToken); //let the method call procees upon successful authorization @@ -49,13 +58,13 @@ public class SecurityInterceptor implements MethodInterceptor{ return returnObj; } - private void authorize(AuthzToken authzToken) throws AuthorizationException { + private void authorize(AuthzToken authzToken, Map<String, String> metaData) throws AuthorizationException { try { boolean isAPISecured = ServerSettings.isAPISecured(); if (isAPISecured) { AiravataSecurityManager securityManager = SecurityManagerFactory.getSecurityManager(); - boolean isAuthz = securityManager.isUserAuthorized(authzToken); + boolean isAuthz = securityManager.isUserAuthorized(authzToken, metaData); if (!isAuthz) { throw new AuthorizationException("User is not authenticated or authorized."); } http://git-wip-us.apache.org/repos/asf/airavata/blob/9c02f24d/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java ---------------------------------------------------------------------- diff --git a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java index a2d032f..e373316 100644 --- a/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java +++ b/modules/commons/src/main/java/org/apache/airavata/common/utils/Constants.java @@ -42,4 +42,10 @@ public final class Constants { public static final String KEYSTORE_PATH = "keystore.path"; public static final String KEYSTORE_PASSWORD = "keystore.password"; public static final String TLS_CLIENT_TIMEOUT = "TLS.client.timeout"; + public static final String API_METHOD_NAME = "api.method.name"; + + //Names of the attributes that could be passed in the AuthzToken's claims map. + public static final String USER_NAME = "userName"; + public static final String EMAIL = "email"; + public static final String ROLE = "role"; } http://git-wip-us.apache.org/repos/asf/airavata/blob/9c02f24d/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml ---------------------------------------------------------------------- diff --git a/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml new file mode 100644 index 0000000..7aa42fe --- /dev/null +++ b/modules/configuration/server/src/main/resources/airavata-default-xacml-policy.xml @@ -0,0 +1,62 @@ +<Policy xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17" PolicyId="airavata-policy-uploaded" + RuleCombiningAlgId="urn:oasis:names:tc:xacml:3.0:rule-combining-algorithm:permit-overrides" Version="1.0"> + <Target/> + <Rule Effect="Permit" RuleId="admin-permit"> + <Target> + <AnyOf> + <AllOf> + <Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-regexp-match"> + <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">/airavata/*</AttributeValue> + <AttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" + Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action" + DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="true"/> + </Match> + </AllOf> + </AnyOf> + </Target> + <Condition> + <Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-is-in"> + <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">admin</AttributeValue> + <AttributeDesignator AttributeId="http://wso2.org/claims/role" + Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" + DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="true"/> + </Apply> + </Condition> + </Rule> + <Rule Effect="Permit" RuleId="user-permit"> + <Target> + <AnyOf> + <AllOf> + <Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-regexp-match"> + <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">^(?:(?! + /airavata/addGateway| + /airavata/deleteteway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/updateGateway| + /airavata/getExperimentStatistics).)*$\r?\n? + </AttributeValue> + <AttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" + Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action" + DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="true"/> + </Match> + </AllOf> + </AnyOf> + </Target> + <Condition> + <Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-is-in"> + <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">Internal/everyone</AttributeValue> + <AttributeDesignator AttributeId="http://wso2.org/claims/role" + Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" + DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="true"/> + </Apply> + </Condition> + </Rule> + <Rule Effect="Deny" RuleId="deny-rule"/> +</Policy> +
