Saturday, February 15, 2020

Prototyping Mule OAuth2 Client Application

Introduction

This article explains how to prototype Mule OAuth 2.0 client application with grant type of "Client Credentials", which is most popular grant type for Mule integration. Most modern APIs enforce OAuth2.0 security policies. OAuth2.0 has the following Grant Types (The detailed explanation can be found here):
  • Authorization Code
  • PKCE
  • Client Credentials
  • Device Code
  • Refresh Token
  • Legacy: Implicit Flow
  • Legacy: Password Grant
This post will cover the following topics:
  • Using postman to retrieve access token
  • Prototyping retrieve access token using cURL
  • Mule flow to retrieve access token
  • Sample flow using the access token with caching scope

OAuth 2.0 With Client Credential Grant Type

In order to access the OAuth2.0 enabled APIs, we first have to retrieve the access token from the Identity Providers. Then we can access the API by passing the access token. The parameters for Client Credential can be the following:
  • grant_type (required)
  • scope (optional)
  • client_id (required)
  • client_secret (required)
In most cases, cilent_id and client_secret are encrypted as Basic Authentication. The encryption can be done using base64 or openssl as the following:
echo -n "${CLIENT_ID}:${CLIENT_SECRET}" | base64
echo -n "${CLIENT_ID}:${CLIENT_SECRET}" | openssl enc -base64
Note: the "-n" option of echo is for not printing the trailing newline character. This is very important.

Using Postman to Retrieve Access Token

The postman is the best tool to do prototyping for the OAuth 2 client. The following snapshot shows the setup of the Postman:
for body:
for Headers:
for Authorization:

cURL Solution

Once we have the postman, the solution of cUrl is very straight forward.
$ cat oauth2-client.sh

#!/bin/bash
#
CLIENT_ID=MY-CLIENT-ID-GOES-HERE-WITHOUT-QUOTE
CLIENT_SECRET=YOUR-CLIENT-SECRET-GOES-HERE-WITHOUT-QUOTE

OAUTH_HEADER=$(echo -n "${CLIENT_ID}:${CLIENT_SECRET}" | base64)

curl -d "grant_type=client_credentials&scope=https://graph.microsoft.com/.default" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -H "Authorization: Basic ${OAUTH_HEADER}" \
     -XPOST https://login.microsoftonline.com/keurig.onmicrosoft.com/oauth2/v2.0/token

Mule Application Solution - Retrieve access_token

The mule application flow for retrieving access token is the following:
The Data-Weave transformation code is the following:
%dw 2.0
output application/x-www-form-urlencoded
---
{
 grant_type: "client_credentials",
 scope: "https://graph.microsoft.com/.default"
}
The request configuration is as the following:

   

The HTTPS connector configuration referred in the request is the following:
The xml configuration is the following:


  
   
    
   
   
    
   
  

As you can see, we pass the client_id and client_secret as the username and password of the basic authentication. This is just base64 encoded string. Here is an example of the response from the retrieval of access token.
{
    "token_type": "Bearer",
    "expires_in": 3599,
    "ext_expires_in": 3599,
    "access_token": "eyJ0eXAi......"
}

Mule Application Solution - Use access_token

The following diagram shows the usage of the access_token. The access_token is passed to the server as header.
As you can see, we need to use cache scope. This allow us to avoid calling the server if the token is not expired. In this case, the token will expire in one hour. Thus our object store TTL should be less then 60 minutes.

Sunday, February 2, 2020

Anypoint Runtime Fabric : Part 1 - Pre-Installation Network Checking

Introduction

In the next few weeks, I will write more details about Mule runtime fabric installation, configuration, and troubleshooting. This article is about to verify the network connectivity before install Anypoint Runtime Fabric (RTF). In order to install Anypoint RTF, the network firewall must open. The details about the port and host whitelisting can be find here: https://docs.mulesoft.com/runtime-fabric/1.4/install-port-reqs.

Tools for Network Connectivity Checking

The following tools should be installed and configure on the Linux system:
  • nslookup
  • curl
  • nc
  • openssl
  • chrony
nslookup must configured properly. To verify nslookup, you can use the following command:
nslookup anypoint.mulesoft.com
The above command should print out something like:
Name: anypoint-prod-936665732.us-east-1.elb.amazonaws.com
Address: 52.87.103.123
Name: anypoint-prod-936665732.us-east-1.elb.amazonaws.com
Address: 3.217.25.79
...
This means it is working. If you find a problem, most likely the file /etc/resolv.conf has the incorrect content. It should look like the following:
$ cat /etc/resolv.conf
# Generated by NetworkManager
search gmcr.com
nameserver 10.64.2.13
nameserver 10.124.240.104
The next tool is the well-known cURL. run the following command:
$ curl -sk https://anypoint.mulesoft.com:443
You should see something like the following:

301 Moved Permanently

301 Moved Permanently


nginx
This is good. If it hung, then we have problem. I have covered the usage of nc and openssl in my previous post

NTP - Network Time Protocol

In order for Anypoint RTF to work, we must make sure NTP is working in the system. Linux use chrony to perform NTP. Run the following command:
chronyc tracking
If the NTP is working correctly, it should print out the something like the following:
Reference ID    : A3EDDA13 (toddadev.your.org)
Stratum         : 2
Ref time (UTC)  : Mon Feb 03 00:18:55 2020
System time     : 0.000235624 seconds slow of NTP time
Last offset     : -0.000094732 seconds
RMS offset      : 0.000081353 seconds
Frequency       : 16.987 ppm slow
Residual freq   : -0.003 ppm
Skew            : 0.062 ppm
Root delay      : 0.025243049 seconds
Root dispersion : 0.000095447 seconds
Update interval : 514.2 seconds
Leap status     : Normal
Note the Leas status must be "Normal", otherwise, Anypoint RTF will be not working. The real for the Leap status is not normal is the network firewal is not open to the RedHat's time server. In this case, we need to use company's internal time server. To do this, modify the file: /etc/chrony.conf
# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
#server 0.rhel.pool.ntp.org iburst
#server 1.rhel.pool.ntp.org iburst
#server 2.rhel.pool.ntp.org iburst
#server 3.rhel.pool.ntp.org iburst
server server 10.70.0.200
...
The line: server server 10.70.0.200 is company's private time server. Make sure comment out all the RedHat's entries. After the modification of the file of /etc/chrony.conf, make sure run the following command:
sudo systemctl restart chronyd
sudo systemctl enable chronyd
You also may verify the sources of the Ntp by the following command:
chronyc sources
This will restart the chrony daemon.

Network Connectivity Test Scripts

Mulesoft provided a scripts totest the network connectivity at here If you have gone through the test in the previous sections, this script should run with the following results.
Once the above procedures are all done, we are read to perform the installation for Anypoint Runtime Fabric. I will cover that in the following articles.

Anypoint Runtime Fabric - SSL Handshaking Troubleshooting

Introduction

Mulesoft's Anypoint Runtime Fabric is gaining momentum in the hybrid deployment model. Since version 1.4.1, the installation, configuration, and management have improved significantly. However, as it involves TLS/SSL, many things can go wrong. This article will provide some technical insights on how to diagnose those issues. In many cases, it may not be the configuration issues. Sometimes, the issues could be related to client code or network configuration. In later section, I have provided java code to test HTTPS REST API. First let's review the architecture of Anypoint Runtime Fabric.
As shown from the above diagram, the TLS/SSL is applied to all the controllers. Let's say the IP addresses are:
10.64.6.65
10.64.6.66
10.64.6.67
On all of the above controllers, the port 443 should be open. And we should be able to connect to all these controllers by using networking tools like nc, openssl, etc.

Verify Controller's TLS/SSL

First, from the client point of view, we need to make sure the port 443 is open and reachable. To do so, we can execute the following command:
$ nc -zv 10.64.6.65 443
Connection to 10.64.6.65 port 443 [tcp/https] succeeded!
As you can see, we can connect the port 443 successfully. nc is a very light and powerful tool for quickly scan the port of the server. Secondly, we can use openssl to verify the TLS/SSL:
$ openssl s_client -connect 10.64.6.65:443
openssl is a heavy weight tool. It can do a lot things, such as, connection verification, ssl certification generation and conversion, etc. The above command will print out information about the server's TLS/SSL certificates, ssh handshaking, cipher, etc. Here are an example:

$ openssl s_client -connect 10.64.6.65:443
CONNECTED(00000003)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance Server CA
verify return:1
depth=0 C = US, ST = Texas, L = Plano, O = "Keurig Dr. Pepper, Inc.", CN = *.gmcr.com
verify return:1
---
Certificate chain
 0 s:/C=US/ST=Texas/L=Plano/O=Keurig Dr. Pepper, Inc./CN=*.gmcr.com
   i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
 1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
   i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=/C=US/ST=Texas/L=Plano/O=Keurig Dr. Pepper, Inc./CN=*.gmcr.com
issuer=/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
---
No client certificate CA names sent
Server Temp Key: ECDH, X25519, 253 bits
---
SSL handshake has read 4087 bytes and written 289 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: 820EF7CA565E40E495662B0D1AAAB24E2AE431B660A86DF7659D2DEFB7B986B1
    Session-ID-ctx:
    Master-Key: 98F3D990B13A2847041E6275682C74F3D371A15C8D0358434957D6B87B865DA9E839AB95950FE6F24E9D5CDD0DE59F93
    TLS session ticket lifetime hint: 7200 (seconds)
    TLS session ticket:
...
    Start Time: 1580667077
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---
closed
The most important information client should notice are the following:
  • CN = *.gmcr.com -- This dictate how client should invoke the service
  • SHA2 High Assurance Server CA -- SHA2 is Secure Hashing Algorithm 2.
  • Protocol : TLSv1.2 -- We are using TLSv1.2, now Anypoint RTF also support TLSv1.3
  • Cipher : ECDHE-RSA-AES128-GCM-SHA256
  • Verify return code: 0 (ok) -- This is very important.
The last line tells us the TLS certificate is valid and can be verified. If the self-signed certificate is applied, the last line will be something like:
Verify return code: 18 (self signed certificate)

Verify Server's Cipher Suites

It is not very uncommon that server and client could not find the common supported ciphers. To handle this, we need to scan server's cipher suite. I use two tools: nmap and cipherscan. There are many free software available, but I find they are very easy to use and very powerful. Wireshark is another very popular tool. If all of the simple tools are exhausted, we can use Wireshark. Here is example using cipherscan:
$ ./cipherscan 10.64.6.65
Warning: target is not a FQDN. SNI was disabled. Use a FQDN or '-servername '
...............
Target: 10.64.6.65:443

prio  ciphersuite                  protocols  pfs                 curves
1     ECDHE-RSA-AES128-GCM-SHA256  TLSv1.2    ECDH,P-256,256bits  prime256v1,secp384r1,secp521r1
2     ECDHE-RSA-AES256-GCM-SHA384  TLSv1.2    ECDH,P-256,256bits  prime256v1,secp384r1,secp521r1
3     AES256-GCM-SHA384            TLSv1.2    None                None
4     DHE-RSA-AES128-GCM-SHA256    TLSv1.2    DH,2048bits         None
5     AES128-GCM-SHA256            TLSv1.2    None                None

Certificate: trusted, 4096 bits, sha256WithRSAEncryption signature
TLS ticket lifetime hint: 7200
NPN protocols: None
OCSP stapling: not supported
Cipher ordering: server
Curves ordering: server - fallback: no
Server supports secure renegotiation
Server supported compression methods: NONE
TLS Tolerance: yes

Intolerance to:
 SSL 3.254           : absent
 TLS 1.0             : PRESENT
 TLS 1.1             : PRESENT
 TLS 1.2             : absent
 TLS 1.3             : absent
 TLS 1.4             : absent
As you can see, it provide supported cipher and TLS protocols. You can down cipherscan from github: https://github.com/mozilla/cipherscan. nmap is another very powerful and easy to use tool. nmap is a bit slow.
$ nmap -sV --script ssl-enum-ciphers -p 443 10.64.6.65
Starting Nmap 7.80 ( https://nmap.org ) at 2020-02-02 13:11 CST
Nmap scan report for hello-earth.kdrp.com (10.64.6.65)
Host is up (0.086s latency).

PORT    STATE SERVICE   VERSION
443/tcp open  ssl/https
| fingerprint-strings:
|   FourOhFourRequest, GetRequest, HTTPOptions:
|     HTTP/1.1 404 NOT FOUND
|     Content-Length: 0
|     Connection: Close
|   RTSPRequest, SIPOptions:
|     HTTP/1.1 400 BAD REQUEST - bad version
|     Content-Length: 0
|_    Connection: Close
| ssl-enum-ciphers:
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
|       TLS_RSA_WITH_AES_256_GCM_SHA384 (rsa 4096) - A
|       TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (dh 2048) - A
|       TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 4096) - A
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       Key exchange (dh 2048) of lower strength than certificate key
|       Key exchange (ecdh_x25519) of lower strength than certificate key
|_  least strength: A
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port443-TCP:V=7.80%T=SSL%I=7%D=2/2%Time=5E371EE9%P=x86_64-apple-darwin1
SF:7.7.0%r(GetRequest,40,"HTTP/1\.1\x20404\x20NOT\x20FOUND\r\nContent-Leng
SF:th:\x200\r\nConnection:\x20Close\r\n\r\n")%r(HTTPOptions,40,"HTTP/1\.1\
SF:x20404\x20NOT\x20FOUND\r\nContent-Length:\x200\r\nConnection:\x20Close\
SF:r\n\r\n")%r(FourOhFourRequest,40,"HTTP/1\.1\x20404\x20NOT\x20FOUND\r\nC
SF:ontent-Length:\x200\r\nConnection:\x20Close\r\n\r\n")%r(RTSPRequest,50,
SF:"HTTP/1\.1\x20400\x20BAD\x20REQUEST\x20-\x20bad\x20version\r\nContent-L
SF:ength:\x200\r\nConnection:\x20Close\r\n\r\n")%r(SIPOptions,50,"HTTP/1\.
SF:1\x20400\x20BAD\x20REQUEST\x20-\x20bad\x20version\r\nContent-Length:\x2
SF:00\r\nConnection:\x20Close\r\n\r\n");

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 46.51 seconds

Verify REST API

From network point view, everything seems working, but client may still have problem to invoke the REST API exposed on the Anypoint RTF. In these cases, we need to check the following. Using postman, if the certificate applied to Anypoint RTF is self-signed, we need to make sure to turn off the ssl verification on the postman.
Still some client are using plain java to invoke REST API. In this case, we can use the following code:
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpsHelloWorld {

 public static void main(String[] args) {
  // TODO Auto-generated method stub
  System.out.println("Hello, World");
  HttpGet request = new HttpGet("https://kknxplvmsftct.gmcr.com/earth/hello");
  request.addHeader("Host", "hello-earth-one.gmcr.com");
  CloseableHttpClient httpClient = HttpClients.createDefault();
  
        try {
          CloseableHttpResponse response = httpClient.execute(request);
            // Get HttpResponse Status
            System.out.println(response.getStatusLine().toString());

            HttpEntity entity = response.getEntity();
            Header headers = entity.getContentType();
            System.out.println(headers);

            if (entity != null) {
                // return it as a String
                String result = EntityUtils.toString(entity);
                System.out.println(result);
            }
        } catch (Exception e) {
          e.printStackTrace();
        }
     
 }

}
add the following dependencies to pom.xml
  
    org.apache.httpcomponents
    httpclient
    4.5.10
  
  
  
      org.springframework
      spring-core
      5.2.3.RELEASE
  
  
  
      org.springframework
      spring-web
      5.2.3.RELEASE
  

Or if you prefer to use REST template, you can use the following example:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;

import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

public class HttpsPostMes {

 public static void main(String[] args) {
  try {
   httpsRequestCall3();
   
  } catch(Exception e) {
   e.printStackTrace();
   
  }
     
 }

 public static void httpsRequestCall3() {
  
     CloseableHttpClient httpClient
       = HttpClients.custom()
         .setSSLHostnameVerifier(new NoopHostnameVerifier())
         .build();
     
     HttpComponentsClientHttpRequestFactory requestFactory  = new HttpComponentsClientHttpRequestFactory();
     
     requestFactory.setHttpClient(httpClient);
     
     String urlOverHttps = "https://kknxplvmsftct.gmcr.com/earth/hello";
     
     HttpHeaders headers = new HttpHeaders();
     headers.add("Host", "hello-earth-one.gmcr.com");
      HttpEntity entity = new HttpEntity<>("header", headers);
     
  
     ResponseEntity response = new RestTemplate(requestFactory).exchange(urlOverHttps, HttpMethod.GET, entity, String.class);
     org.springframework.http.HttpStatus code = response.getStatusCode();
     
     System.out.println(code);
     
     System.out.print(response);
  
 } 
  

}

Take Aways

In this article, I have covered the following:
  1. Basic Anypoint runtime fabric's controllers
  2. Tools to verify servers ports, SSL protocols, and ciphers
    • nc
    • openssl
    • nmap
    • ciphjerscan
  3. java code to invoke REST API using https

Saturday, October 26, 2019

Mule Integration - Write Elegant Code

Introduction

Recently, I encountered the following piece of Mule Dataweave 2.0 function code.

fun getEmployeePovince(worker) = getStateAbbrevation(worker) match {
     case "AB" -> "AB"
     case "BC" -> "BC"
     case "MB" -> "MB"
     case "NB" -> "NB"
     case "NL" -> "NL"
     case "NS" -> "NS"
     case "NT" -> "NT"
     case "NU" -> "NU"
     case "ON" -> "ON"
     case "PE" -> "PE"
     case "QC" -> "QC"
     case "SK" -> "SK"
     case "YT" -> "YT"
     else -> "ZZ"
    }

The purpose of the above function is to get home province's code of a Canadian employee. The Canadian province code is like, BC for British Columbia, ON for Ontario, etc. The input is an xml object which contains information about the employee. If the worker's province code is not among the list, default to "ZZ".

The above code works and about to go to production. However, this kind of code is really not very cool to say the least. It is just an amateur's code!

Improvement One

First of all, the gist of this kind of problem is to find a match from a given list of Strings. Mule Dataweave 2.0 provides a function, namely find. The documentation can be found here.. The find function works like the following:
['aa', 'bb', 'cc'] find 'xy'  //return [], empty array
['aa', 'bb', 'cc', 'aa'] find 'aa' //return [0, 3]
Now the code using find function should be clear. If the return array from the find is empty (isEmpty(findFunction)), use the input, otherwise, use default, "ZZ".

The following is the datawave code:

%dw 2.0
var CanadianProvinces = ["BC", "MB", "NB", "NL", "NS", "NT", "NU", "NT", "NU", "ON", "PE", "QC", "SK", "YT"]
var pv = payload.state
fun findAKey(aKey) = CanadianProvinces find aKey

output application/json
---
{
 province: if(isEmpty(findAKey(pv))) "ZZ" else pv,
}
The above code is much cleaner. We put the constants of the province code into an array, and use find function. However the above code is still not good for maintenance. Let's see if I want to use the same to logic to US state's code. We have to modify the dataweave code. We can further improve the code. That is to put the constant into property file.

Improvement Two

First, we put the two constants to the yaml property file as the following:
provinces: ["BC", "MB", "NB", "NL", "NS", "NT", "NU", "NT", "NU", "ON", "PE", "QC", "SK", "YT"]
constants:
   default: "ZZ"

%dw 2.0
var provinces = p('provinces')
var pv = payload.state
var dp = p('constants.default')
fun findAKey(aKey) = provinces find aKey

output application/json
---
{
 provice: if(isEmpty(findAKey(pv))) dp else pv,
}

Take Aways

  1. Two dataweave 2.0 functions: switch and find
  2. Array constants in yaml property file
  3. When we encounter some strange code, we should think about the improvement. There is always ways to write elegant code.

Tuesday, September 3, 2019

Install JSON Plugin In Anypoint Studio

Introduction

JSON Plugin is very useful tool for editing and verifying JSON schemas or json examples data in our API deployment. Unfortunately, it does not come with Anypoint Studio. To me, AnypointStudio should more or less behave like Eclipse which allow us to drag and paste any available plugin. This short article describe the procedure to install the plugin.

Installation of JSON Plugin

First download the plugin zip file from this website to ~/Download. The file name should be like:

rw-r--r--@   1 gl17  staff   112K Sep  3 14:38 jsonedit-repository-0.9.7.zip

Second, in AnypointStudio, Help --> Install New Software --> Add

Note, select Achive and from local file.

After installation, restart the AnypointStudion. Now if you open any json file, the Studio will validate the file.

Saturday, August 24, 2019

Manage Mule Runtime Using Linux Services On RHEL 7

Introduction

This article describes the procedures to enable Mule runtime as systemd service for Mule standalone runtime clustering on-premises or in private clouds. Since RHEL 7, the systemd init system is a must. The traditional init.d approach is obsolete. For details about the systemd and Unit files, you may refer to this article.

Assumptions

  • Mule standalone runtime is installed at /opt/mule/runtime/current
  • Mule runtime can be started and stoped by the command /opt/mule/runtime/current/bin/mule start | stop
  • mule user has sudo permission

Create Unit File

First, we need to create a file, mule.service at /etc/systemd/system, with the following contents:
# file: /etc/systemd/system/mule.service
# Systemd unit file for mule standalone runtime
[Unit]
Description=Mule Runtime Standalone Runtime
After=syslog.target network.target

[Service]
Type=forking
WorkingDirectory=/opt/mule/runtime/current
Environment=JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64
Environment=MULE_HOME=/opt/mule/runtime/current
TasksMax=infinity
LimitNOFILE=65335

ExecStart=/opt/mule/runtime/current/bin/mule start
ExecStop=/opt/mule/runtime/current/bin/mule stop

User=mule
Group=mule

RestartSec=10
Restart=always

[Install]
WantedBy=multi-user.target

$ cd /etc/systemd/system/
$ sudo chmod 644 mule.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable mule.service
The above command will enable the mule.service. This procedure simply creates a line in the folder of: /etc/systemd/system/system-update.target.wants
systemd-readahead-drop.service -> /usr/lib/systemd/system/systemd-readahead-drop.service
Now, we are ready to start the mule runtime as a service. To do this, we must first stop the running Mule runtime:
$ cd /opt/mule/runtime/current
$ bin/mule stop
Now, run the following command:
$ sudo systemctl start mule.service
It may take sometime before we get the prompt back. After that we can check whether the mule runtime is running or not by the following command:
$ sudo systemctl status mule.service
● mule.service - Mule Runtime Standalone Runtime
   Loaded: loaded (/etc/systemd/system/mule.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2019-08-24 15:46:04 CDT; 2h 45min ago
 Main PID: 17555 (wrapper-linux-x)
   CGroup: /system.slice/mule.service
           ├─17555 /opt/mule/runtime/current/lib/boot/exec/wrapper-linux-x86-64 /opt/mule/runtime/current/conf/wrapper.conf wrapper.s...
           └─17569 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64/jre/bin/java -Dmule.home=/opt/mule/runtime/current -D...

Aug 24 15:45:41 wp37mulerte01.aci.awscloud systemd[1]: Starting Mule Runtime Standalone Runtime...
Aug 24 15:45:41 wp37mulerte01.aci.awscloud mule[17439]: MULE_HOME is set to /opt/mule/runtime/current
Aug 24 15:45:41 wp37mulerte01.aci.awscloud mule[17439]: MULE_BASE is set to /opt/mule/runtime/current
Aug 24 15:45:42 wp37mulerte01.aci.awscloud mule[17439]: Starting Mule Enterprise Edition...
Aug 24 15:46:04 wp37mulerte01.aci.awscloud mule[17439]: Waiting for Mule Enterprise Edition.......................
Aug 24 15:46:04 wp37mulerte01.aci.awscloud mule[17439]: running: PID:17555
Aug 24 15:46:04 wp37mulerte01.aci.awscloud systemd[1]: Started Mule Runtime Standalone Runtime.
As you can see, the mule runtime is running. If you want to know the deatils about the mule runtime, you can use the following command:
$ sudo systemctl -l status mule.service
The -l option will print out the arguments passed to the JVM. To view the history of stop/start of the Mule runtime, we can use the following command:
$ sudo journalctl -u mule.service
-- Logs begin at Tue 2019-08-06 23:11:10 CDT, end at Sat 2019-08-24 18:39:13 CDT. --
Aug 22 21:40:03 wd35mulerte01.aci.awscloud systemd[1]: Starting Mule Runtime Standalone Runtime...
Aug 22 21:40:03 wd35mulerte01.aci.awscloud mule[31293]: MULE_HOME is set to /opt/mule/runtime/current
Aug 22 21:40:03 wd35mulerte01.aci.awscloud mule[31293]: MULE_BASE is set to /opt/mule/runtime/current
Aug 22 21:40:05 wd35mulerte01.aci.awscloud systemd[1]: mule.service: control process exited, code=exited status=1
Aug 22 21:40:05 wd35mulerte01.aci.awscloud systemd[1]: Failed to start Mule Runtime Standalone Runtime.
Aug 22 21:40:05 wd35mulerte01.aci.awscloud systemd[1]: Unit mule.service entered failed state.
Aug 22 21:40:05 wd35mulerte01.aci.awscloud systemd[1]: mule.service failed.
Aug 22 21:40:14 wd35mulerte01.aci.awscloud systemd[1]: Stopped Mule Runtime Standalone Runtime.
Aug 22 21:52:39 wd35mulerte01.aci.awscloud systemd[1]: Starting Mule Runtime Standalone Runtime...
Aug 22 21:52:39 wd35mulerte01.aci.awscloud mule[32610]: MULE_HOME is set to /opt/mule/runtime/current
Aug 22 21:52:39 wd35mulerte01.aci.awscloud mule[32610]: MULE_BASE is set to /opt/mule/runtime/current
Aug 22 21:52:40 wd35mulerte01.aci.awscloud mule[32610]: Starting Mule Enterprise Edition...
Aug 22 21:53:03 wd35mulerte01.aci.awscloud mule[32610]: Waiting for Mule Enterprise Edition.........................
Aug 22 21:53:04 wd35mulerte01.aci.awscloud mule[32610]: running: PID:32750
Aug 22 21:53:04 wd35mulerte01.aci.awscloud systemd[1]: Started Mule Runtime Standalone Runtime.
Aug 22 21:54:15 wd35mulerte01.aci.awscloud systemd[1]: Stopping Mule Runtime Standalone Runtime...
Aug 22 21:54:15 wd35mulerte01.aci.awscloud mule[633]: MULE_HOME is set to /opt/mule/runtime/current
Aug 22 21:54:15 wd35mulerte01.aci.awscloud mule[633]: MULE_BASE is set to /opt/mule/runtime/current
Aug 22 21:54:15 wd35mulerte01.aci.awscloud mule[633]: Stopping Mule Enterprise Edition...
Aug 22 21:54:18 wd35mulerte01.aci.awscloud systemd[1]: Stopped Mule Runtime Standalone Runtime.
Aug 22 21:55:43 wd35mulerte01.aci.awscloud systemd[1]: Starting Mule Runtime Standalone Runtime...
Aug 22 21:55:44 wd35mulerte01.aci.awscloud mule[912]: MULE_HOME is set to /opt/mule/runtime/current
Aug 22 21:55:44 wd35mulerte01.aci.awscloud mule[912]: MULE_BASE is set to /opt/mule/runtime/current
Aug 22 21:55:45 wd35mulerte01.aci.awscloud mule[912]: Starting Mule Enterprise Edition...
Aug 22 21:56:08 wd35mulerte01.aci.awscloud mule[912]: Waiting for Mule Enterprise Edition.........................
Aug 22 21:56:08 wd35mulerte01.aci.awscloud mule[912]: running: PID:1091
Aug 22 21:56:08 wd35mulerte01.aci.awscloud systemd[1]: Started Mule Runtime Standalone Runtime.

That is it. It is very helpful to study the commands of systemctl and journalctl.

Sunday, August 18, 2019

Two-Way SSL In Mule Application - Part 2

Introduction

In my previous article in DZone or here, I omitted the procedure to create a trust store for the application. This is important if the applications are deployed to the CloudHub.

In this article, I will describe the procedures to create the trust store and how to configure the HTTPS request for Mule application

Create A Trust Store FOR MULE HTTPS Request

The procedures to import the server's PEM certificate to a trust store are the following.

First, we will create a trust store using the following command:

keytool -genkey -keyalg RSA -alias cyberark-poc -keystore truststore.ks
Enter anything. They are not important as we will delete it.

Second, delete the content of the trust store just created:

keytool -delete -alias cyberark-poc -keystore truststore.ks
Third, import the server's certificate:
keytool -import -v -trustcacerts -alias cyberark-server -file SERVER-CERT.pem -keystore truststore.ks
Now, copy the truststore.ks to Mule application project /src/main/resources

HTTPS Request Configuration

The following is the complete HTTPS Request configuration:
 
  
   
    
    
   
  
 
Note: I put both client.pfx and truststore in the directory of /src/main/resources. You may put them into different directory. In that case, you need to give the full path relative to the Mule Application Project, such as ssh/cert/client.pfx.

The Key Takeaways

The best practice for certificates manipulation is:
  1. If the deployment is on-prem, import servers' certificates to cacert. In this way if the server's certificate is expired, we just need to reimport, not code change is required.
  2. If the deployment is CloudHub, we have to import the servers' certificate to a truststore as described in this article.
  3. Use JKS format for the trust store used in the HTTPS request. It is most popular one.

Anypoint Studio Error: The project is missing Munit lIbrary to run tests

Anypoint Studio 7.9 has a bug. Even if we following the article: https://help.mulesoft.com/s/article/The-project-is-missing-MUnit-libraries-...