Wednesday, January 25, 2023

Configuring a Distributed API Manager Deployment with Gateway and Control Plane

In the distributed setup, the API-M server profiles are deployed as separate API-M nodes. Followings are the APIM nodes you can have in a distributed deployment support for our pattern.

  • Gateway Worker Node - API-M nodes running the Gateway profile.
  • Control Plane Node - API-M nodes running the Control Plane profile. The Control Plane includes the Traffic Manager, Key Manager, Publisher, and Developer Portal components.


1. Install WSO2 API Manager

  • Download the WSO2 API Manager.
  • Create copies of the API-M distribution for the individual profiles

2. Install and configure the databases

You can create the required databases for the API-M deployment in a separate server and point to the databases from the respective nodes.

Below are the default databases which will be used within API Manager.
  • WSO2AM_DB - WSO2 API Manager has this database keeping its specific API-M related data.
  • WSO2SHARED_DB - This database contains the registry and user management data.
  • WSO2CARBON_DB - This database has the internal data related to the product.
Databases used by API-M profiles:

WSO2AM_DBWSO2SHARED_DBWSO2CARBON_DB
Control Plane profileYesYesYes
Gateway profileNoYesYes

It is recommended to use an industry-standard RDBMS such as Oracle, PostgreSQL, MySQL, MS SQL, etc. WSO2 products are shipped with scripts for creating the required tables in all the required databases: The scripts for creating tables for API-M, user management, and registry data are stored in the <API-M_HOME>/dbscripts directory.

In this example i'm using PostgreSQL.

3. Create and import SSL certificates

Create an SSL certificate for each of the WSO2 API-M nodes and import them to the keystore and the truststore. This ensures that hostname mismatch issues in the certificates will not occur.

Note: The same primary keystore should be used for all API Manager instances to decrypt the registry resources.

For more details:


4. Configure and start the profiles

4.1 Configure the Gateway Nodes

Follow these steps to configure the Gateway to communicate with the Control Plane.

Open the <API-M_HOME>/repository/conf/deployment.toml file of the Gateway node.
Add the following configurations to the deployment.toml file.

Note: Update the parameters as necessary.

--------------------------------------------------------------------------------------------------------
[server]
hostname = "{{apim gateway lb host}}"
node_ip = "{{node_ip}}"
server_role = "gateway-worker"
offset= 0

[user_store]
type = "database_unique_id"

[super_admin]
username = "$secret{admin_username}"
password = "$secret{admin_password}"
create_admin_account = true

#Databases

[database.apim_db]
type = "postgre"
url = "jdbc:postgresql://{{db host}}:5432/apim_db"
username = "$secret{wso2am_db_username}"
password = "$secret{wso2am_db_password}"
driver = "org.postgresql.Driver"
validationQuery = "SELECT 1"
pool_options.maxActive = 50
pool_options.maxWait = 30000

[database.shared_db]
type = "postgre"
url = "jdbc:postgresql://{{db host}}:5432/shared_db"
username = "$secret{wso2shared_db_username}"
password = "$secret{wso2shared_db_password}"
driver = "org.postgresql.Driver"
validationQuery = "SELECT 1"
pool_options.maxActive = 50
pool_options.maxWait = 10000
pool_options.validationInterval = 10000

[database.local]
type = "postgre"
url = "jdbc:postgresql://{{db host}}:5432/carbon_db"
username = "$secret{wso2carbon_db_username}"
password = "$secret{wso2carbon_db_password}"
driver = "org.postgresql.Driver"
validationQuery = "SELECT 1"


 # JWT Generation
[apim.jwt]
enable = "true"
encoding = "base64"
generator_impl = "org.wso2.carbon.apimgt.keymgt.token.JWTGenerator"
claim_dialect = "http://wso2.org/claims"
header = "X-JWT-Assertion"
signing_algorithm = "SHA256withRSA"
enable_user_claims = "false"
claims_extractor_impl = "org.wso2.carbon.apimgt.impl.token.ExtendedDefaultClaimsRetriever"

[apim.sync_runtime_artifacts.gateway]
gateway_labels = ["Default"]
artifact_retriever = "DBRetriever"
deployment_retry_duration = 15000
data_retrieval_mode = "sync"
event_waiting_time = 5000


[apim.key_manager]
service_url = "https://{{apim control plane lb host}}/services/"
username = "$ref{super_admin.username}"
password = "$ref{super_admin.password}"

[apim.throttling]
enable_data_publishing = true
enable_policy_deploy = true
enable_blacklist_condition = true
enable_persistence = true
username = "$ref{super_admin.username}"
password = "$ref{super_admin.password}"
service_url = "https://{{apim control plane lb host}}/services/"
throttle_decision_endpoints = ["tcp://{{apim control plane 1 host}}:5672","tcp://{{apim control plane 2 host}}:5672"]
enable_unlimited_tier = true
enable_header_based_throttling = false
enable_jwt_claim_based_throttling = false
enable_query_param_based_throttling = false

[[apim.throttling.url_group]]
traffic_manager_urls = ["tcp://{{apim control plane 1 host}}:9611"]
traffic_manager_auth_urls = ["ssl://{{apim control plane 1 host}}:9711"]

[[apim.throttling.url_group]]
traffic_manager_urls = ["tcp://{{apim control plane 2 host}}:9611"]
traffic_manager_auth_urls = ["ssl://{{apim control plane 2 host}}:9711"]


[transport.http]
properties.port = 9763
properties.proxyPort = 80

[transport.https]
properties.port = 9443
properties.proxyPort = 443

[apim.analytics]
enable = false
auth_token = "auth_token"

# Caches
[apim.cache.gateway_token]
enable = true
expiry_time = 15

[apim.cache.resource]
enable = true

[apim.cache.jwt_claim]
enable = true
expiry_time = 900

[apim.oauth_config]
enable_outbound_auth_header = true
auth_header = "Authorization"

[apim.cors]
allow_origins = "*"
allow_methods = ["GET","PUT","POST","DELETE","PATCH","OPTIONS"]
allow_headers = ["authorization","Access-Control-Allow-Origin","Content-Type","SOAPAction"]
allow_credentials = false


[oauth.grant_type.token_exchange]
enable = true
allow_refresh_tokens = true
iat_validity_period = "1h"

# Condition check for whether the Certification Import Option is enabled
[keystore.primary]
file_name = "primary.pfx"
password = "$secret{primaryKeyStorePassword}"
alias = "wso2carbon"
key_password = "$secret{primaryKeyPassword}"
type = "PKCS12"

[keystore.internal]
file_name = "internal.pfx"
password = "$secret{internalKeyStorePassword}"
alias = "wso2carbon"
key_password = "$secret{internalKeyPassword}"
type = "PKCS12"

[keystore.tls]
file_name = "tls.pfx"
password = "$secret{tlsKeyStorePassword}"
alias = "wso2carbon"
key_password = "$secret{tlsKeyPassword}"
type = "PKCS12"


[truststore]
file_name = "client-truststore.jks"
password = "$secret{truststrorePassword}"
alias = "symmetric.key.value"
algorithm = "AES"

[secrets]
admin_username = "[{{ admin_username }}]"
admin_password = "[{{ admin_password }}]"
wso2am_db_username = "[{{ wso2am_db_username }}]"
wso2am_db_password = "[{{ wso2am_db_password }}]"
wso2shared_db_username = "[{{ wso2shared_db_username }}]"
wso2shared_db_password = "[{{ wso2shared_db_password }}]"
wso2carbon_db_username = "[{{ wso2carbon_db_username }}]"
wso2carbon_db_password = "[{{ wso2carbon_db_password }}]"
truststrorePassword = "[{{truststore.storePassword}}]"

primaryKeyStorePassword  = "[{{primary.privateKeyPasssword}}]"
primaryKeyPassword  = "[{{primary.privateKeyPasssword}}]"
internalKeyStorePassword  = "[{{internal.privateKeyPasssword}}]"
internalKeyPassword  = "[{{internal.privateKeyPasssword}}]"
tlsKeyStorePassword  = "[{{tls.privateKeyPasssword}}]"
tlsKeyPassword  = "[{{tls.privateKeyPasssword}}]"

---------------------------------------------------------------------------------------------------------

4.2 Configure the Control Plane Nodes

Follow these steps to configure the Control Plane to communicate with the Gateway.

Open the <API-M_HOME>/repository/conf/deployment.toml file of the Gateway node.
Add the following configurations to the deployment.toml file.

Note: Update the parameters as necessary.

------------------------------------------------------------------------------------------------------------

[server]
hostname = "{{apim control plane lb host}}"
node_ip = "{{node_ip}}"
server_role = "control-plane"
base_path = "${carbon.protocol}://${carbon.host}:${carbon.management.port}"

[user_store]
type = "database_unique_id"

[super_admin]
username = "$secret{admin_username}"
password = "$secret{admin_password}"
create_admin_account = true

#Databases

[database.apim_db]
type = "postgre"
url = "jdbc:postgresql://{{db host}}:5432/apim_db"
username = "$secret{wso2am_db_username}"
password = "$secret{wso2am_db_password}"
driver = "org.postgresql.Driver"
validationQuery = "SELECT 1"
pool_options.maxActive = 50
pool_options.maxWait = 30000

[database.shared_db]
type = "postgre"
url = "jdbc:postgresql://{{db host}}:5432/shared_db"
username = "$secret{wso2shared_db_username}"
password = "$secret{wso2shared_db_password}"
driver = "org.postgresql.Driver"
validationQuery = "SELECT 1"
pool_options.maxActive = 50
pool_options.maxWait = 10000
pool_options.validationInterval = 10000

[database.local]
type = "postgre"
url = "jdbc:postgresql://{{db host}}:5432/carbon_db"
username = "$secret{wso2carbon_db_username}"
password = "$secret{wso2carbon_db_password}"
driver = "org.postgresql.Driver"
validationQuery = "SELECT 1"

[tenant_mgt]
enable_email_domain = true

[apim.devportal]
url = "https://{{apim control plane lb host}}/devportal"

[transport.http]
properties.port = 9763
properties.proxyPort = 80

[transport.https]
properties.port = 9443
properties.proxyPort = 443

 # JWT Generation
[apim.jwt]
enable = "true"
encoding = "base64"
generator_impl = "org.wso2.carbon.apimgt.keymgt.token.JWTGenerator"
claim_dialect = "http://wso2.org/claims"
header = "X-JWT-Assertion"
signing_algorithm = "SHA256withRSA"
enable_user_claims = "false"
claims_extractor_impl = "org.wso2.carbon.apimgt.impl.token.ExtendedDefaultClaimsRetriever"

[apim.sync_runtime_artifacts.publisher]
artifact_saver = "DBSaver"
publish_directly_to_gateway = "false"


[[apim.gateway.environment]]
name = "Default"
type = "hybrid"
display_in_api_console = true
description = "This is a hybrid gateway that handles both production and sandbox token traffic."
show_as_token_endpoint_url = true
service_url = "https://{{apim gateway lb host}}/services/"
ws_endpoint = "ws://{{apim gateway lb host}}:9099"
wss_endpoint = "wss://{{apim gateway lb host}}:8099"
http_endpoint = "http://{{apim gateway lb host}}"
https_endpoint = "https://{{apim gateway lb host}}"
username= "${admin.username}"
password= "${admin.password}"
websub_event_receiver_http_endpoint = "http://{{apim gateway lb host}}:9021"
websub_event_receiver_https_endpoint = "https://{{apim gateway lb host}}:8021"
# provider = "wso2"

# Event Hub configurations
[apim.event_hub]
enable = true
username= "$ref{super_admin.username}"
password= "$ref{super_admin.password}"
service_url = "https://{{apim control plane lb host}}/services/"
event_listening_endpoints = ["tcp://{{apim control plane 1 host}}:5672"]
event_duplicate_url = ["tcp://{{apim control plane 2 host}}:5672"]

[[apim.event_hub.publish.url_group]]
urls = ["tcp://{{apim control plane 1 host}}:9611"]
auth_urls = ["ssl://{{apim control plane 1 host}}:9711"]

[[apim.event_hub.publish.url_group]]
urls = ["tcp://{{apim control plane 2 host}}:9611"]
auth_urls = ["ssl://{{apim control plane 2 host}}:9711"]

# key manager implementation
[apim.key_manager]
service_url = "https://{{apim control plane lb host}}/services/"

[apim.analytics]
enable = false
auth_token = "auth_token"

[apim.cache_invalidation]
enabled = true
domain = "control-plane-domain"

# Caches
[apim.cache.gateway_token]
enable = true
expiry_time = 15

[apim.cache.resource]
enable = true

[apim.cache.jwt_claim]
enable = true
expiry_time = 900

[apim.oauth_config]
enable_outbound_auth_header = true
auth_header = "Authorization"

[apim.cors]
allow_origins = "*"
allow_methods = ["GET","PUT","POST","DELETE","PATCH","OPTIONS"]
allow_headers = ["authorization","Access-Control-Allow-Origin","Content-Type","SOAPAction"]
allow_credentials = false


[[event_listener]]
id = "token_revocation"
type = "org.wso2.carbon.identity.core.handler.AbstractIdentityHandler"
name = "org.wso2.is.notification.ApimOauthEventInterceptor"
order = 1

[event_listener.properties]
notification_endpoint = "https://{{apim control plane lb host}}/internal/data/v1/notify"
username = "${super_admin.username}"
password = "${super_admin.password}"
'header.X-WSO2-KEY-MANAGER' = "default"

[oauth.grant_type.token_exchange]
enable = true
allow_refresh_tokens = true
iat_validity_period = "1h"


# Condition check for whether the Certification Import Option is enabled
[keystore.primary]
file_name = "primary.pfx"
password = "$secret{primaryKeyStorePassword}"
alias = "wso2carbon"
key_password = "$secret{primaryKeyPassword}"
type = "PKCS12"

[keystore.internal]
file_name = "internal.pfx"
password = "$secret{internalKeyStorePassword}"
alias = "wso2carbon"
key_password = "$secret{internalKeyPassword}"
type = "PKCS12"

[keystore.tls]
file_name = "tls.pfx"
password = "$secret{tlsKeyStorePassword}"
alias = "wso2carbon"
key_password = "$secret{tlsKeyPassword}"
type = "PKCS12"

[truststore]
file_name = "client-truststore.jks"
password = "$secret{truststrorePassword}"
alias = "symmetric.key.value"
algorithm = "AES"

[secrets]
admin_username = "[{{ admin_username }}]"
admin_password = "[{{ admin_password }}]"
wso2am_db_username = "[{{ wso2am_db_username }}]"
wso2am_db_password = "[{{ wso2am_db_password }}]"
wso2shared_db_username = "[{{ wso2shared_db_username }}]"
wso2shared_db_password = "[{{ wso2shared_db_password }}]"
wso2carbon_db_username = "[{{ wso2carbon_db_username }}]"
wso2carbon_db_password = "[{{ wso2carbon_db_password }}]"
truststrorePassword = "[{{truststore.storePassword}}]"

primaryKeyStorePassword  = "[{{primary.privateKeyPasssword}}]"
primaryKeyPassword  = "[{{primary.privateKeyPasssword}}]"
internalKeyStorePassword  = "[{{internal.privateKeyPasssword}}]"
internalKeyPassword  = "[{{internal.privateKeyPasssword}}]"
tlsKeyStorePassword  = "[{{tls.privateKeyPasssword}}]"
tlsKeyPassword  = "[{{tls.privateKeyPasssword}}]"

-----------------------------------------------------------------------------------------------------


Open the server's /etc/hosts file and map the hostnames to IPs.

Format: 
<GATEWAY-LB-IP> gw.wso2.com
<GATEWAY-1-IP> gw-1.wso2.com
<GATEWAY-2-IP> gw-2.wso2.com
<CONTROL-PLANE-LB-IP> cp.wso2.com
<CONTROL-PLANE-1-IP> cp-1.wso2.com
<CONTROL-PLANE-2-IP> cp-2.wso2.com


4.3 Configure carbon.xml

Configure carbon.xml file if you using custom keystores as below.

<Security>
        <!--
            KeyStore which will be used for encrypting/decrypting passwords
            and other sensitive information.
        -->
        <KeyStore>
            <!-- Keystore file location-->
            <Location>${carbon.home}/repository/resources/security/primary.pfx</Location>
            <!-- Keystore type (JKS/PKCS12 etc.)-->
            <Type>PKCS12</Type>
            <!-- Keystore password-->
            <Password>$secret{primaryKeyStorePassword}</Password>
            <!-- Private Key alias-->
            <KeyAlias>wso2carbon</KeyAlias>
            <!-- Private Key password-->
            <KeyPassword>$secret{primaryKeyPassword}</KeyPassword>
        </KeyStore>

        <!--
            The KeyStore which is used for encrypting/decrypting internal data.
            This block is read by Carbon Crypto Service.
        -->
        <InternalKeyStore>
            <!-- Keystore file location-->
            <Location>${carbon.home}/repository/resources/security/internal.pfx</Location>
            <!-- Keystore type (JKS/PKCS12 etc.)-->
            <Type>PKCS12</Type>
            <!-- Keystore password-->
            <Password>$secret{internalKeyStorePassword}</Password>
            <!-- Private Key alias-->
            <KeyAlias>wso2carbon</KeyAlias>
            <!-- Private Key password-->
            <KeyPassword>$secret{internalKeyPassword}</KeyPassword>
        </InternalKeyStore>

        <UserStorePasswordEncryption>InternalKeyStore</UserStorePasswordEncryption>

        <!--
            System wide trust-store which is used to maintain the certificates of all
            the trusted parties.
        -->
        <TrustStore>
            <!-- trust-store file location -->
            <Location>${carbon.home}/repository/resources/security/client-truststore.jks</Location>
            <!-- trust-store type (JKS/PKCS12 etc.) -->
            <Type>JKS</Type>
            <!-- trust-store password -->
            <Password>$secret{truststrorePassword}</Password>
        </TrustStore>



<Security>

4.4 Configure load balancers

You need to properly configure load balancers fronting the two Control Plane nodes and two Gateway nodes.


4.5 Start the API-M nodes

Before starts the servers,

Execute the control plane optimization:
cd <API-M_HOME>/bin/
sh profileSetup.sh -Dprofile=control-plane

Execute the gateway optimization:
cd <API-M_HOME>/bin/
sh profileSetup.sh -Dprofile=gateway-worker

Once you have successfully configured all the API-M nodes in the deployment, you can start the servers.

Starting the Gateway nodes

Open a terminal, navigate to the <API-M-GATEWAY-HOME>/bin folder, and execute the following command

cd <API-M_HOME>/bin/
sh api-manager.sh -Dprofile=gateway-worker

Start the Control Plane nodes

Open a terminal, navigate to the <API-M-CONTROL-PLANE-HOME>/bin folder, and execute the following command:
cd <API-M_HOME>/bin/
sh api-manager.sh -Dprofile=control-plane


5. Sample APIM deployment architecture in Azure cloud.

This is the sample architecture of the APIM deployment in Azure using Azure VMs, Azure scaleset, Azure Load-balancer and Azure Active Directory.




Monday, January 2, 2023

How to Integrate Azure AD for API Authentication in WSO2 APIM

Here I'm covering how to configure WSO2 API Manager 4.1.0 federated authentication with Azure Active Directory step by step(OpenID connect SSO flow)

1. Azure configuration.

1.1 Azure App Registration.

First login to your Azure portal with your credentials. If you don't have an account you can create a free trial account to check this scenario.

Once you successfully logged-in you can see as below.

Click on the Azure Active Directory and select which AD you want to use. If you don't have created AD then you can create a new one for this by clicking on the Create Directory icon. Enter the required details and create new one. It will take few minuted to create an AD for you.

Once all done with new creation select the Azure AD and select App registration on the left navigation panel to register an Open ID client.

Click on the New registration and enter the following details:

  • Name: wso2-apim (any desired name as you want)
  • Supported Account Types: Accounts in this organizational directory only
  • Redirect URI: Web , https://<hostname>:9443/commonauth (change the hostname according to your environment)

And click on Register.

After successful creation you will be redirected to a general info page of the registered application. On this page copy and save the Application (client) ID as it will need for API Manager configurations.


Then click on the Certificates & secrets section in the left side panel and select the New client secret to generate a secret for our newly created wso2-apim application. 

Enter the following details and click Add. 

  • Description: WSO2APIMDEV OIDC Secret (You can change this as per your choice)
  • Expires: Select your desired time period


Then click on Add.
Copy and save the generated client secret value.

After that click on the API permissions in the left navigation panel to configure the OpenID and Profile scope permissions to your application.


Click on the Add a permission button to add new permissions. Once you click, you will see a side window popped up with the following options.


Choose the Microsoft Graph option and select Delegated permissions on the following screen. Then select the following permissions. 

  • email 
  • openid 
  • Profile 


Click on Add permissions. Leave the existing permissions as it is. Then you can see the all configured API permissions as below.



1.2 Group Registration.

Since we are configuring the SSO flow with Azure AD, we will be creating a security group called Subscriber to represent the subscriber role in the Azure service. For Publisher and Creator roles you need to create separate groups respectively. 

Note: You can create Roles in the azure service and assign them to Users. In addition, you can also create Groups in Azure AD and assign it to your Users. 

Click on the Groups on your AD and there click on New group button. Then you will see it as below. Fill the followings in there. 
  • Group type: Security 
  • Group name: Subscriber 
  • Group description: Group for subscriber

Click on the No owners selected. On the prompted side window, select on owner's Azure accounts you need to add. 
Then click on the No members selected. As above step selects desired members you want to add. 
And finally click on Create. You can see the newly created group as below.



Take a note of the Object id value. 

Note: This is to map the Groups with the API Manager’s internal roles. We will be using the Object IDs of the groups to map the roles in the API Manager. 

Create Another Groups for Publisher and Creator as well. 

1.2.1 Add Groups claim.

Now we have to configure our Azure application to populate the Groups claim in the ID token. Go to the Azure application (wso2-apim) and click on Token configurations.


Click on the Add group claim and tick the security groups. Then expand the ID and choose the Group ID and enable Emit groups as role claims and click on Add.


Now all the set with the Azure configurations. But before moving forward, we will make a note on the Authorization and Token endpoints of our Azure application. We will need these endpoints when configuring an Identity provider in the API Manager.



Go to the Overview page of the Azure application and click on the Endpoints button as above. This will pop a side-window with all the necessary endpoints of our application. Make a note on the Authorization v2 and Token v2 endpoints. In addition to the above-set, also make a note on the OpenID Connect metadata document endpoint.

If you go to the endpoint URL mentioned in the OpenID Connect metadata document, you will find a set of metadata to configure an Identity Provider in the API Manager.



2. WSO2 API Manager configuration.

First of all you need to enable "enable_email_domain" property in the API Manager. Reason behind it "preferred_username" claim in Azure ID token represented with email username. So we will be enabling email username in our API Manager server to provision and log-in to the Store nodes.

Go to <apim-home>/repository/conf and open deployment.toml file and add the following entry.

[tenant_mgt] enable_email_domain= true
Add the following entry for the email regex validation since the default validation only checks the 30 characters. [user_store] username_java_regex = '^[a-zA-Z0-9.-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}'

2.1 Identity Provider Creation.

Go to the Carbon Management console, and log-in with the admin credentials.


Select the Add under the Identity Providers and input the followings: 

  • Identity Provide Name: AzureADIDP 
  • Display Name: Azure AD IDP 
  • Choose IDP Certificate Type: Use IDP JWKS Endpoint 
  • Identity Provider’s JWKS Endpoint: the JWKS URI found in the OpenID Connect metadata document 



Next, expand the Claim Configurations > Basic Claim configuration > Define Custom Claim Dialect > Add Claim Mapping and add the following mappings 

  • preferred_username : http://wso2.org/claims/displayName 
  • roles : http://wso2.org/claims/role 
and 
  • User ID Claim URI: preferred_username 
  • Role Claim URI: roles 


Expand the Role configurations and click on the Add Role Mapping. Paste the Object ID acquired from the Subscriber group in the Azure service and map it with our internal subscriber role

  • The Object ID of Subscriber Group : Internal/subscriber

Given below is a sample Role mapping configuration.


Update these for all other groups as well like below.


Then, expand the Federated Authenticators > OAuth2/OpenID Connect Configurations and perform the following

  • Enable OAuth2 / OpenID Connect: true
  • Default: true
  • Client ID: the Client ID of our WSO2APIM Azure app
  • Client Secret: the generated secret of our WSO2APIM Azure app
  • Authorization Endpoint URL: the authorization_endpoint in the OpenID Connect metadata document
  • Token Endpoint URL: the token_endpoint in the OpenID Connect metadata document
  • Userinfo Endpoint URL: the userinfo_endpoint in the OpenID Connect metadata document
  • Additional Query Parameters: scope=openid profile email


And finally, expand the Just-In-Time Provisioning accordion and configure as follows.


And finally click on Register.


2.2 Service Provider Creation.

From API Manager 4.x onwards, both Publisher and Devportal nodes are configured with OIDC SSO flows as default. 

Hence, go to both Publisher and Devportal nodes and click on the Sign-in button to automatically create and register a Service Provider for the OIDC SSO flow. You don't have to log-in to the portals, the above-mentioned process is to create the respective Service Providers in the API Manager. 

Navigate to the Carbon Management console and sign-in with the Admin credentials. 

Click on List under Service Providers section to list the automatically created Service Providers respective to Publisher: apim_publisher and Devportal: apim_devportal.


Click the Edit of the apim_devportal and expand the Claim Configurations. Select "Define Custom Claim Dialect" and make the following changes 

  • preferred_username : http://wso2.org/claims/displayName : Requested 


Next, expand the Local & Outbound Authentication Configuration and select the AzureADIDP as the Federated Authentication and make the following changes


Do the same configurations for apim_publisher. 

Now we are done with all configs. We have now successfully configured OpenID Connect Federated Authentication between Azure AD and Single Sign-On with OpenID Connect with APIM Devportal and Publisher. 

Go to the Publisher or devporal and click on Sign-In. You will be redirected to the Microsoft’s login page to enter the credentials. Enter the credentials of our Microsoft User and then allow the attributes on the consent screen to continue with the federation and provision of the users to the WSO2
















Saturday, April 9, 2022

Adding custom headers filter in WSO2 Micro Gateway

Filters are a set of execution points in the request and response flow that intercept the request before it goes to the backend service and intercepts the response before forwarding it to the client. Filters are applied to all the APIs exposed via Microgateway. Custom filter can be engaged to Microgateway runtime using the Microgateway toolkit. If there is a common functionality required by all the APIs exposed via the Microgateway then a custom filter written in ballerina language can be used for that

Use case:

There are few mandatory headers with maximum character length that need to pass to the API. This filter will not affect if the resource has a "/callback"

Sample Headers: AppId (Length:10),  AppVersion (Length:10), RequestId (Length:36)

Save the below content as header_validation_filter.bal in location <PROJECT_HOME>/extensions folder.

import ballerina/http;

import ballerina/lang.'int;

import ballerina/log;

import ballerina/stringutils;

import ballerina/system;


public type HeaderValidationFilter object {


    public function filterRequest(http:Caller caller, http:Request req, http:FilterContext context) returns boolean {


        log:printInfo("<HeaderValidationFilter> : header validation custom filter enabled");

        // string headersArray = "AppId:10, AppVersion:10, RequestId:36";

        string headersString = system:getEnv("headersArray");

        string[] array = stringutils:split(headersString, ",");

        string resourceName = req.rawPath;

        boolean isCallback = stringutils:contains(resourceName,"callback");

        string? remoteHostName = caller.getRemoteHostName();


        string remoteHost = caller.remoteAddress.host;

        log:printInfo("<HeaderValidationFilter> Resource: " + resourceName);

        log:printInfo("<HeaderValidationFilter> isCallback: " + isCallback.toString());        

        log:printInfo("<HeaderValidationFilter> Client: " + remoteHost+" : "+remoteHostName.toString());

        string xFwrd = req.getHeader("X-FORWARDED-FOR");

        string realIp = req.getHeader("X-Real-Ip");

        log:printInfo("<HeaderValidationFilter> X-FORWARDED-FOR: " + xFwrd);

        log:printInfo("<HeaderValidationFilter> X-realIp: " + realIp);


        if(isCallback){

            return true;

        } 

        foreach string header in array {

            log:printDebug("<HeaderValidationFilter> : validating mandatory header : " + header);

            string[] tempArry = stringutils:split(header, ":");

            string headerKey = tempArry[0].trim();

            int | error headerVal = 'int:fromString(tempArry[1]);


            if (req.hasHeader(headerKey)) {

                string apiHeader = req.getHeader(headerKey);

                if (headerVal is int) {

                    if (apiHeader.length() === 0 || apiHeader.length() > headerVal) {

                        log:printInfo("<HeaderValidationFilter> : header " + headerKey + " validation failed");

                        http:Response res = new;

                        res.statusCode = 400;

                        json payload = {errorCode: "ERR_1000006", errorMessage: "Header validation failure", errorDescription: "Header validation failed for : " + headerKey};

                        res.setJsonPayload(payload);

                        var status = caller->respond(res);

                        return false;

                    }

                }

            } else {

                log:printInfo("<HeaderValidationFilter> : header " + headerKey + " not present");

                http:Response res = new;

                res.statusCode = 400;

                json payload = {errorCode: "ERR_1000001", errorMessage: "Missing mandatory parameters", errorDescription: "One or few mandatory parameters are missing in the request"};

                res.setJsonPayload(payload);

                var status = caller->respond(res);

                return false;

            }

        }

        return true;

    }

    public function filterResponse(http:Response response, http:FilterContext context) returns boolean {

        return true;

    }

};

Define the custom filter in the deployment-config.toml file as below.

[[filters]]

      name = "HeaderValidationFilter"

      position = 2

  • name: Name of the filter object defined in the .bal file. Ex: CustomFiler
  • position: The position of the filter to be engaged. By default, microgateway has the following set of filters. By defining the position it can engage the custom filter in between any default filter chain

Then build the project. Custom filter will be engaged in the runtime

Monday, January 17, 2022

Iterator and aggregator mediator example using call mediator with blocking='true'

This example showcase how to use a call mediator with blocking=true (Synchronous way - wait for the API response and call next) inside the iterator mediator and aggregate response inside the aggregator mediator.

Sample Request payload used:

<Bank>

<Account>21424124214</Account>

<Account>35353553343</Account>

<Account>74564352352</Account>

<Account>86643526546</Account>

<Account>35465626565</Account>

<Account>25454625466</Account>

<Account>23564342424</Account>

<Account>23453654665</Account>

</Bank>

Sample code:

<?xml version="1.0" encoding="UTF-8"?>

<proxy xmlns="http://ws.apache.org/ns/synapse"

       name="test"

       transports="http https"

       startOnLoad="true">

   <description/>

   <target>

      <inSequence>

         <property name="countRequests"

                   expression="count(//Account)"

                   scope="default"

                   type="STRING"

                   description="countRequests"/>

         <iterate id="IterateAccountCreate" expression="//Account" sequential="true">

            <target>

               <sequence>

                  <payloadFactory media-type="xml">

                     <format>

                        <echo:echoString xmlns:echo="http://echo.services.core.carbon.wso2.org">

                           <in xmlns="">$1</in>

                        </echo:echoString>

                     </format>

                     <args>

                        <arg evaluator="xml" expression="//Account/text()"/>

                     </args>

                  </payloadFactory>

                  <property name="TRANSPORT_HEADERS"

                            scope="axis2"

                            action="remove"

                            description="TRANSPORT_HEADERS"/>

                  <property name="OperationName"

                            value="echoString"

                            scope="default"

                            type="STRING"

                            description="OperationName"/>

                  <header name="To" scope="default" action="remove"/>

                  <header name="Action" scope="default" value="urn:echoString"/>

                  <header name="SOAPAction" scope="transport" value="urn:echoString"/>

                  <log category="DEBUG" description="***Request***">

                     <property name="request" expression="$body"/>

                  </log>

                  <call blocking="true">

                     <endpoint>

                        <http method="POST" uri-template="http://prabod:8313/services/echo"/>

                     </endpoint>

                  </call>

                  <log category="DEBUG"

                       separator="***Response***"

                       description="***Response***">

                     <property name="create_instanties_response" expression="$body"/>

                  </log>

  <loopback/>

               </sequence>

            </target>

         </iterate>

      </inSequence>

      <outSequence>

<aggregate description="Aggregate response" id="IterateAccountCreate">

<completeCondition timeout="10">

</completeCondition>

<onComplete expression="//ns:echoStringResponse"

xmlns:ns="http://echo.services.core.carbon.wso2.org">

<log description="***aggregated***" separator="***aggregated***">

<property expression="$body" name="aggregated_body" />

</log>

<respond/>

</onComplete>

</aggregate>

      </outSequence>

   </target>

</proxy>

In this example, I'm calling the mock backend to test the scenario.
http://prabod:8313/services/echo

This will iterate all the account numbers and call the backend by passing the account number inside the iterator mediator. Finally, aggregate all the responses into one response payload inside aggregator mediator. In this scenario, I need this to be synchronous. In that case, we need to use the call mediator with blocking="true" and the aggregator mediator needs to be inside out-sequence.


Monday, August 9, 2021

Changing the WSO2 Management console's interface

The user interfaces of every Carbon product allows you to configure, monitor, tune, and maintain the product. The components that formulate the design and style of these user interfaces are defined in resource (JAR) files. 

The user interface of every Carbon product consists of two layers:

  • The common product layout/design inherited from the Carbon platform: All the common templates, styles (CSS files), and images are stored in the Carbon core UI bundle, which is named org.wso2.carbon.ui-<version-number>.jar (<version-number> is the particular version of the bundle). This bundle is responsible for the overall look and feel of the entire Carbon platform.

  • The styles/images unique to each product: Each Carbon product (that is built on Carbon kernel) has another style bundle, which contains all the overriding style sheets and images: org.wso2.<product-name>.styles-<version-number>.jar.

You can customize the user interfaces by modifying these resource files. You need to create a fragment bundle for the original resource file. Then, you can pack the modified resource files in the required bundle. The files in the required bundle will get precedence and will override the files in the original bundle.

You can use this same technique to customize any aspect of the user interfaces. The advantage of this technique is that you will not lose your customizations when you apply official patches to the product by replacing the original bundles.



Follow the steps below to customize the above management console by changing the logo.

  1. Open the <PRODUCT_HOME>/repository/components/plugins/ directory. You need to find the bundle that contains the resource files that you want to modify. In this case, the logo and the related CSS files are contained in the org.wso2.carbon.ui_<version-number>.jar file. Copy the org.wso2.carbon.ui_<version-number>.jar file to a separate location on your computer, and extract the file. Note the symbolic name of this bundle, which is org.wso2.carbon.ui_<version-number>
  2. Create a new Maven project using your IDE. Be sure to include the symbolic name of the original bundle that you extracted in the previous step (which is org.wso2.carbon.ui_<version-number>) in the Maven project name. For example, you can use org.wso2.carbon.ui_<version-number>_patch as the Maven project name. 
  3. Add the following content to the pom.xml file of the  org.wso2.carbon.ui_<version-number>_patch project. In this pom.xml file, be sure to replace the <version-number> of org.wso2.carbon.ui_<version-number>_patch with the correct version value. (ex: org.wso2.carbon.ui_4.5.3_patch)

  4. Create directories in your Maven project as explained below.
    1. Create the /web folder under the /src/main/resources directory of the org.wso2.carbon.ui_<version-number>_patch project.
    2. Then, create the /admin directory under /web.
    3. Finally, create the /css, /images, and /layout directories under /admin.


  5. Create a new CSS file (e.g. customizations.css with the following content and Add the customizations.css file to the org.wso2.carbon.ui_<version-number>_patch/src/main/resources/web/admin/css/ directory. This file contains the logo customization styles.

  6. Locate the template.jsp file that is in the org.wso2.carbon.ui_<version-number>.jar bundle, which you extracted in step 1 above. You will find template.jsp file inside the org.wso2.carbon.ui_<version-number>.jar/web/admin/layout/ directory. Then, copy this file to the org.wso2.carbon.ui_<version-number>_patch/src/main/resources/web/admin/layout/ directory.
  7. Add below line into this template.jsp file. <link href="../admin/css/customizations.css" rel="stylesheet" type="text/css" media="all"/>

  8. You can update the Title and the favicon as well.

  9. Add the new image as the new logo (e.g.  new-logo.png) to the org.wso2.carbon.ui_<version-number>_patch/src/main/resources/web/admin/images/ directory.
  10. Create another Maven project using your IDE. Be sure to include the symbolic name of the original bundle that you extracted in step 1 above (which is org.wso2.carbon.ui_<version-number>) in the project name. For example, you can use org.wso2.carbon.ui_<version-number>_fragment as the Maven project name. This creates a project for the fragment bundle. Since the symbolic name of the original bundle is org.wso2.carbon.ui, the fragment host value of this bundle should be the same (e.g. org.wso2.carbon.ui_<version-number>_fragment). This fragment bundle will not contain anything (expect the pom.xml file) when it is built.
  11. Add the following content to the pom.xml file of the org.wso2.carbon.ui_<version-number>_fragment project. In this pom.xml file, replace the <version-number>  of org.wso2.carbon.ui_<version-number>_patch and org.wso2.carbon.ui_<version-number>_fragment with the correct version value. This pom.xml file of the fragment bundle defines properties, which includes the required bundle value (ex: org.wso2.carbon.ui_<version-number>_patch)
     

  12. Now you can build the two projects. Open a terminal, navigate to the relevant project directory (listed above), and execute the following command: mvn clean install.
    • org.wso2.carbon.ui_<version-number>_fragment 

    • org.wso2.carbon.ui_<version-number>_patch 

  13. Once the project is built, copy the two JAR files listed below (from the <PROJECT_HOME>/target/ directory) to the <PRODUCT_HOME>/dropins/ directory.
    • org.wso2.carbon.ui_<version-number>_fragment-1.0.0.jar 

    • org.wso2.carbon.ui_<version-number>_patch-1.0.0.jar

  14.  Restart the WSO2 product server.
  15. Access the management console of your WSO2 product using the following URL: https://localhost:9443/carbon/You view the new logo, which the patch bundle contains as shown below.