Wednesday, November 26, 2014

How to hide actual backend wsdl by providing internal WSDL in WSO2 API Manager

In WSO2 API Manager we can provide wsdl when we create API. And users may be able to access it from UI. When we store API in registry we will rewrite endpoints to match with API created. But still users can type API url and wsdl and hit backend wsdl. For this we can suggest synapse configuration based solution. For this i added filter on top of resource and check request is coming for wsdl. If it contains wsdl url then we can forward request to registry path of internal wsdl. Sometimes editing synapse configurations manually will cause to some problems(when we update through publisher it will discard changes etc). So we normally dont recommend that but in this case we do not have other option. 
 
As a solution for that we can automate this using velosity template file. Then we don't have to do this per each API Manually. For this we can use API name, context and version parameters inside velosity configuration and generate filter according to that. Sample configuration would be something like this. Please note here we should have connection to registry ( some of our clients don't mount registry for gateway). We need to explain limitations of this approach when we reply to client.

<filter source="get-property('To')" regex=".*?wsdl.*">
<log level="custom">
<property name="end" expression="get-property('To')"/>
</log>
<send>
<endpoint>
<address uri="http://10.100.1.65:9763
/registry/resource/_system/governance/apimgt/applicationdata/wsdls/admin--rrr1.0.0.wsdl"/>
</endpoint>
</send>
</filter>


Monday, November 17, 2014

How to use custom authentication header and pass it as auth header to back end server(in addition to bearer token).

In this article we will describe how we can use custom authentication header and pass it as auth header to backend server.

You can add a mediation extension [1], and have a custom global sequence in the API gateway which will assign Authorization header the value of your basic authentication.

<sequence name="WSO2AM--Ext--In" xmlns="http://ws.apache.org/ns/synapse"> 
<property name="Authentication" expression="get-property('transport', 'Authentication')"/> 
<property name="Authorization" expression="get-property('Authentication')" scope="transport" type="STRING"/> 
<property name="Authentication" scope="transport" action="remove" /> 
</sequence> 


In order to add the custom mediation, visit '/repository/deployment/server/synapse-configs/default/sequences' and create an xml file (Ex: global_ext.xml) to contain your mediation extension.
Then include above synapse configuration in that xml. (I have attached the custom global sequence xml here).

When you invoke your Rest API via a RESTclient, configure that client to have a custom header(Ex:Authentication) for your basic authentication credentials and configure 'Authorization' header to contain the bearer token for the API.

So, what will happen will be something like this:
Client (headers: Authorization, Authentication) -> Gateway (drop: Authorization, convert: Authentication-Authorization) -> Backend


[1]https://docs.wso2.com/display/AM150/Adding+a+Mediation+Extension

Friday, November 14, 2014

How to enable mutual SSL connection between WSO2 API Manager gateway and key manager

In WSO2 API Manager we will do service calls from gateway to key manager to validate tokens.
For this we will use key validation client. Lets add following code and build jar file.


package org.wso2.carbon.apimgt.gateway.handlers.security.keys;
import edu.emory.mathcs.backport.java.util.Arrays;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.axiom.soap.SOAPHeaderBlock;
import org.apache.axis2.AxisFault;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ServiceContext;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.httpclient.Header;
import org.wso2.carbon.apimgt.api.model.URITemplate;
import org.wso2.carbon.apimgt.gateway.handlers.security.APISecurityConstants;
import org.wso2.carbon.apimgt.gateway.handlers.security.APISecurityException;
import org.wso2.carbon.apimgt.gateway.internal.ServiceReferenceHolder;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.APIManagerConfiguration;
import org.wso2.carbon.apimgt.impl.dto.APIKeyValidationInfoDTO;
import org.wso2.carbon.apimgt.keymgt.stub.validator.APIKeyValidationServiceAPIManagementException;
import org.wso2.carbon.apimgt.keymgt.stub.validator.APIKeyValidationServiceStub;
import org.wso2.carbon.utils.CarbonUtils;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

public class APIKeyValidatorClient {

private static final int TIMEOUT_IN_MILLIS = 15 * 60 * 1000;
private APIKeyValidationServiceStub clientStub;
private String username;
private String password;
private String cookie;

public APIKeyValidatorClient() throws APISecurityException {
APIManagerConfiguration config = ServiceReferenceHolder.getInstance().getAPIManagerConfiguration();
String serviceURL = config.getFirstProperty(APIConstants.API_KEY_MANAGER_URL);
// username = config.getFirstProperty(APIConstants.API_KEY_MANAGER_USERNAME);
// password = config.getFirstProperty(APIConstants.API_KEY_MANAGER_PASSWORD);
/* if (serviceURL == null || username == null || password == null) {
throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR,
"Required connection details for the key management server not provided");
}*/
try {

ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null);

clientStub = new APIKeyValidationServiceStub(ctx, serviceURL + "APIKeyValidationService");

ServiceClient client = clientStub._getServiceClient();

setMutualAuthHeader(client,"admin");

Options options = client.getOptions();

options.setTimeOutInMilliSeconds(TIMEOUT_IN_MILLIS);

options.setProperty(HTTPConstants.SO_TIMEOUT, TIMEOUT_IN_MILLIS);

options.setProperty(HTTPConstants.CONNECTION_TIMEOUT, TIMEOUT_IN_MILLIS);

options.setCallTransportCleanup(true);

options.setManageSession(true);

} catch (AxisFault axisFault) {

throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR,

"Error while initializing the API key validation stub", axisFault);

} catch (Exception e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

}

}
public APIKeyValidationInfoDTO getAPIKeyData(String context, String apiVersion, String apiKey,
String requiredAuthenticationLevel, String clientDomain,
String matchingResource, String httpVerb) throws APISecurityException {
// CarbonUtils.setBasicAccessSecurityHeaders(username, password,
// true, clientStub._getServiceClient());
if (cookie != null) {
clientStub._getServiceClient().getOptions().setProperty(HTTPConstants.COOKIE_STRING, cookie);
}
try {
List headerList = (List)clientStub._getServiceClient().getOptions().getProperty(org.apache.axis2.transport.http.HTTPConstants.HTTP_HEADERS);
Map headers = (Map) MessageContext.getCurrentMessageContext().getProperty(
org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
if (headers != null && headers.get("activityID")!=null) {
headerList.add(new Header("activityID", (String)headers.get("activityID")));
}
clientStub._getServiceClient().getOptions().setProperty(org.apache.axis2.transport.http.HTTPConstants.HTTP_HEADERS, headerList);
org.wso2.carbon.apimgt.impl.dto.xsd.APIKeyValidationInfoDTO dto =
clientStub.validateKey(context, apiVersion, apiKey,requiredAuthenticationLevel, clientDomain,
matchingResource, httpVerb);
ServiceContext serviceContext = clientStub.
_getServiceClient().getLastOperationContext().getServiceContext();
cookie = (String) serviceContext.getProperty(HTTPConstants.COOKIE_STRING);
return toDTO(dto);
}
catch (APIKeyValidationServiceAPIManagementException ex){

throw new APISecurityException(APISecurityConstants.API_AUTH_FORBIDDEN,
"Resource forbidden", ex);
}catch (Exception e) {
throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR,
"Error while accessing backend services for API key validation", e);
}
}
private APIKeyValidationInfoDTO toDTO(
org.wso2.carbon.apimgt.impl.dto.xsd.APIKeyValidationInfoDTO generatedDto) {
APIKeyValidationInfoDTO dto = new APIKeyValidationInfoDTO();
dto.setSubscriber(generatedDto.getSubscriber());
dto.setAuthorized(generatedDto.getAuthorized());
dto.setTier(generatedDto.getTier());
dto.setType(generatedDto.getType());
dto.setEndUserToken(generatedDto.getEndUserToken());
dto.setEndUserName(generatedDto.getEndUserName());
dto.setApplicationName(generatedDto.getApplicationName());
dto.setEndUserName(generatedDto.getEndUserName());
dto.setConsumerKey(generatedDto.getConsumerKey());
dto.setValidationStatus(generatedDto.getValidationStatus());
dto.setApplicationId(generatedDto.getApplicationId());
dto.setApplicationTier(generatedDto.getApplicationTier());
dto.setApiPublisher(generatedDto.getApiPublisher());
dto.setApiName(generatedDto.getApiName());
dto.setScopes(generatedDto.getScopes() == null ? null : new HashSet(Arrays.asList(generatedDto.getScopes())));
return dto;
}
public ArrayList getAllURITemplates(String context, String apiVersion
) throws APISecurityException {

// CarbonUtils.setBasicAccessSecurityHeaders(username, password,
// true, clientStub._getServiceClient());
if (cookie != null) {
clientStub._getServiceClient().getOptions().setProperty(HTTPConstants.COOKIE_STRING, cookie);
}
try {
org.wso2.carbon.apimgt.api.model.xsd.URITemplate[] dto =
clientStub.getAllURITemplates(context, apiVersion);
ServiceContext serviceContext = clientStub.
_getServiceClient().getLastOperationContext().getServiceContext();
cookie = (String) serviceContext.getProperty(HTTPConstants.COOKIE_STRING);
ArrayList templates = new ArrayList();
for (org.wso2.carbon.apimgt.api.model.xsd.URITemplate aDto : dto) {
URITemplate temp = toTemplates(aDto);
templates.add(temp);
}
return templates;
} catch (Exception e) {
throw new APISecurityException(APISecurityConstants.API_AUTH_GENERAL_ERROR,
"Error while accessing backend services for API key validation", e);
}
}
private URITemplate toTemplates(
org.wso2.carbon.apimgt.api.model.xsd.URITemplate dto) {
URITemplate template = new URITemplate();
template.setAuthType(dto.getAuthType());
template.setHTTPVerb(dto.getHTTPVerb());
template.setResourceSandboxURI(dto.getResourceSandboxURI());
template.setUriTemplate(dto.getUriTemplate());
template.setThrottlingTier(dto.getThrottlingTier());
return template;
}
private static void setMutualAuthHeader(ServiceClient serviceClient, String username) throws Exception {
OMNamespace omNamespace =
OMAbstractFactory.getOMFactory().createOMNamespace("http://mutualssl.carbon.wso2.org", "m");
SOAPHeaderBlock mutualsslHeader = OMAbstractFactory.getSOAP12Factory().createSOAPHeaderBlock("UserName", omNamespace);
mutualsslHeader.setText(username);
mutualsslHeader.setMustUnderstand("0");
serviceClient.addHeader(mutualsslHeader);
}
}


Then once you build jar copy jar file to API Manager
 cp target/org.wso2.carbon.apimgt.gateway-1.2.2.jar /home/sanjeewa/work/170deployment/newdep/test/wso2am-1.7.0-1/repository/components/plugins/org.wso2.carbon.apimgt.gateway_1.2.2.jar


You should share same trust store or need to export cert to gateway from key manager.

Then enable SSL in WSO2 server. Add following to wso2server.sh

    -Djavax.net.ssl.keyStore="$CARBON_HOME/repository/resources/security/wso2carbon.jks" \
    -Djavax.net.ssl.keyStorePassword="wso2carbon" \
    -Djavax.net.ssl.trustStore="$CARBON_HOME/repository/resources/security/client-truststore.jks" \
    -Djavax.net.ssl.trustStorePassword="wso2carbon" \
    -Djavax.net.ssl.keyAlias="wso2carbon" \


This custom authenticator to makes Identity Server compatible with mutual ssl, so you would need to download it too. You could find the source code of the authenticator from here(https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/components/authenticators/mutual-ssl-authenticator/4.2.0/).

Build above mentioned code and copy custom authenticator to /repository/components/dropins/ folder which is handling the mutual authentication in the server backend. This should copied to key manager node.

Then restart both servers by pointing gateway to use key manager as key management service.

Invoke APIs and you are now connected to key manager from gateway using mutual ssl. To verify this you can put wrong credentials in gateway side and restart server.




Monday, November 3, 2014

How to change logged in user password in WSO2 Carbon based products

If you need to call user admin service and perform this operation you can use following information.
Service URL
https://sanjeewa-ThinkPad-T530:9443/services/UserAdmin

PayLoad
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsd="http://org.apache.axis2/xsd">
   <soapenv:Header/>
   <soapenv:Body>
      <xsd:changePasswordByUser>
         <xsd:oldPassword>testsanjeewa</xsd:oldPassword>
         <xsd:newPassword>sanjeewa</xsd:newPassword>
      </xsd:changePasswordByUser>
   </soapenv:Body>
</soapenv:Envelope>
Other possible solution is change password from management console user interface.
Visit following URL https://10.100.1.65:9443/carbon And go to change password window.
Home > Configure > Users and Roles > Change Password

API Manager distributed deployment best practices - API gateway Deployment in DMZ and MZ

Normally when we deploy API Manager in different zones we need to follow security related best practices. Here in this post we will briefly describe about API Manager gateway deployment in DMZ and MZ.

How API Gateway and key manager communication happens
In API Manager we have authentication handler to authenticate all incoming requests. From authentication handler we will will initiate token validation flow. We do have extension point to authentication handler. So if you need to implement some custom flow you can write new handler and use it. In key manager side we have exposed web service and thrift service for this(key validation service). We have two options to select when we call key manager from gateway.
01. Web service call.
In this case we will do a web service call from gateway to key manager. For this call we will Https and we will be using transport layer security for this. for this gateway will authenticate with key manager by using configured username/password in api-manager.xml file.
02. Thrift call.
In this case we will do a thrift service call from gateway to key manager. For this call also we will be using transport layer security(for login). And gateway will authenticate with key manager(thrift server) by using configured username/password in api-manager.xml file.

How to secure deployment(API gateway) in DMZ from common attacks
We have configurations files, run time artifacts and required jar file in repository directory.
We can use secure vault(https://docs.wso2.com/display/Carbon420/WSO2+Carbon+Secure+Vault) to protect repository/conf directory. Then attacker will not get sensitive data like user name, passwords and important urls. With this we will be able to recover configurations even if attacker got file system access.
And if we can keep open 8280 and 8243 then we don't need to expose management urls to outside world. So external users cannot use management services perform server side operations.
If we are having API gateway with worker manager separated then, only workers will reside in DMZ. Even if attacker destroyed synapse configuration in worker nodes still manager node and svn repository will have original configurations. So we can easily recover from this type of situation.
And we can enable java security manager to avoid some common form of attacks(jar file modifications etc).

Please see attached image to identify call between MZ and DMZ.








We have few call between MZ and DMZ
Key validation call from gateway to key manager(connect over https).
Token generation request from gateway to key manager(connect over https).
Artifact synchronize call to svn server(connect over https).
Call from gateway to back end services hosted in MZ(connect over https or http).
Cluster communication between workers and manager node(tcp level cluster messages).



Enable web service key validation and session affinity - WSO2 API Manager deployment in AWS

In clustered environment following issue can happen if we didn't enabled session affinity.
TID: [0] [AM] [2015-11-01 23:31:42,819] WARN {org.wso2.carbon.apimgt.keymgt.service.thrift.APIKeyValidationServiceImpl} - Invalid session id for thrift authenticator. {org.wso2.carbon.apimgt.keymgt.service.thrift.APIKeyValidationServiceImpl}
TID: [0] [AM] [2015-11-01 23:31:42,820] WARN {org.wso2.carbon.apimgt.gateway.handlers.security.thrift.ThriftKeyValidatorClientPool} - Login failed.. Authenticating again.. {org.wso2.carbon.apimgt.gateway.handlers.security.thrift.ThriftKeyValidatorClientPool}
TID: [0] [AM] [2015-11-01 23:31:42,821] ERROR {org.wso2.carbon.apimgt.gateway.handlers.security.APIAuthenticationHandler} - API authentication failure {org.wso2.carbon.apimgt.gateway.handlers.security.APIAuthenticationHandler}

When API gateway receives API call we do token validation call to key management service running on key management server. First we will authenticate with key management service and then do secure call to validate token. This key management service is running on all API Manager nodes. When client call to validate token it will authenticate with service running in one server and actual validation service call going to other server. To resolve this issue please follow below instructions.

Set following properties in API Manager configuration file and restart servers. With this we will be using web service call between gateway and key manager to validate token. Most of load balancers cannot route thrift messages in session aware way. So we will use web service call for that.

<KeyValidatorClientType>WSClient</KeyValidatorClientType>
<EnableThriftServer>false</EnableThriftServer>
Also you need to configure key management server URL properly in configurations(see following configuration).


<APIKeyManager>
        <ServerURL>https://test.wso2.com:9443/services/&lt;/ServerURL>
To enable session affinity. Enable session affinity and enable application generated session cookies in load balancer level. Also set cookie name as JSESSIONID. Then it will route requests in session aware manner.