The purpose of this blog : To identify certificates in the keystore with less than 30 days remaining until expiration and dynamically update all expiring certificates and send the updated certificate via email.
Prerequired:
SAP BTP and Integration Suite capabilities Cloud Integration.
Firstly, I can trigger the process with a CPI service using POSTMAN or run it based on a time interval with a timer. I will proceed with the timer approach. If you prefer, you can set it to check every 15 days.
Under normal circumstances, we check for expired certificates either when an error occurs in the integration or when we feel the need to verify their expiration status. Our goal is to have one or multiple certificates loaded into the keystore automatically updated at a predetermined time, ensuring that our processes no longer encounter errors due to certificate expiration.
The API we will use: https://{Account Short Name}-tmn.{SSL Host}.{region}.hana.ondemand.com/api/v1/KeystoreEntries
AND
https://{Account Short Name}-tmn.{SSL Host}.{region}.hana.ondemand.com/api/v1/CertificateResources
1-CM_Headers
Our service works with a CSRF token, so we need to obtain the token first. In the initial content modifier, we store the request headers of the service.
2-RR_FetchToken
A defined user must access CPI as an authorized user. After defining security materials, credentials are provided in the flow.
3-RR_KeystoreEntries
4-MM_KeystoreEntries
The left side of the mapping is the XSD we created from the KeystoreEntries OData service. I previously shared information about which fields should be selected. The right side of the mapping is the XSD structure I want to output.
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="RootExpiring">
<xs:complexType>
<xs:sequence>
<xs:element name="RootExpiring" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="CertificateNumber" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="xs:string" name="CertificateName" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="xs:short" name="Validation" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="xs:string" name="Type" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
The Groovy script I use to identify certificates with less than 30 days remaining:
def String get30DaysAfter(String arg1){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
Date dateNow= new Date()+30
return dateFormat.format(dateNow)+""
}
5-Router
If there is no certificate that will expire after the mapping, the 'no update' will be observed and the flow will be escalated.
//RootExpiring = ''
6- CM_EscaleteProcess
7-Iterating Splitter
In order to update multiple certificates, we need to split them based on RootExpiring. We will send them to the service one by one.
<RootExpiring>
<RootExpiring>
<CertificateNumber>x1</CertificateNumber>
<CertificateName>a</CertificateName>
<Validation>2024-02-21</Validation>
<Type>Certificate</Type>
</RootExpiring>
</RootExpiring>
<RootExpiring>
<CertificateNumber>x2</CertificateNumber>
<CertificateName>b</CertificateName>
<Validation>2024-02-21</Validation>
<Type>Certificate</Type>
</RootExpiring>
8-CM_Cert_Inf
It will take the host address from the certificateName field. Therefore, when importing a certificate, we need to give the host address as the alias name.
9-GS_Encoded
import java.security.cert.X509Certificate
import java.util.Base64
import javax.net.ssl.SSLPeerUnverifiedException
import javax.net.ssl.SSLSession
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import com.sap.gateway.ip.core.customdev.util.Message
def processData(Message message) {
try {
// Get host and port from message properties
def host = message.getProperty("host") as String
def port = message.getProperty("port") as Integer
def factory = SSLSocketFactory.getDefault() as SSLSocketFactory
def socket = factory.createSocket(host, port) as SSLSocket
def session = socket.getSession()
X509Certificate cert = session.peerCertificates[0] as X509Certificate
def systemDomainName = cert.issuerDN.name
def encodedDERValue = Base64.getEncoder().encodeToString(cert.encoded)
// Set Properties
message.setProperty("systemDomainName", systemDomainName)
message.setProperty("encodedDERValue", encodedDERValue)
return message
} catch (SSLPeerUnverifiedException e) {
throw new Exception("${host} did not present a valid cert.")
}
}
10-CM_EncodedBody
11-RR_CertificateResources
fingerprintVerified=true:
The true value indicates that the certificate fingerprint has been verified and is valid.
returnKeystoreEntries=false:
This setting determines whether keystore entries will be returned during a process or query. A false value means that the keystore records will not be returned as a result of the operation.
update=true:
if you want to update a certificate or configuration, this setting is set to true.
The CertificateNumber value we keep in the property was mapped with the hexalias field in the mapping. This way, we can obtain the hexalias values resulting from the mapping.
In the next step, there is a classic logging Groovy script.
12-Iterating Splitter
In the next step, I will adjust the fields in the latest build to format my mail properly.
I don't want the RooxExpiring field, which contains unwanted tags in the XML produced from the process call and my mail format. Therefore, I used a splitter and converted the XML to JSON.
Input:
<RootExpiring>
<RootExpiring>
<CertificateNumber>x1</CertificateNumber>
<CertificateName>a</CertificateName>
<Validation>2024-02-21</Validation>
<Type>Certificate</Type>
</RootExpiring>
</RootExpiring>
</RootExpiring>
Output After XML to JSON Converter:
{
"CertificateNumber": "x1",
"CertificateName": "a",
"Validation": "2024-02-21",
"Type": "Certificate"
}
13-GS_MailBody
import com.sap.gateway.ip.core.customdev.util.Message;
import groovy.json.JsonSlurper;
def Message processData(Message message) {
// JSON body
def body = message.getBody(java.lang.String) as String;
// Parse JSON
def jsonSlurper = new JsonSlurper();
def jsonObj = jsonSlurper.parseText(body);
// Check if JSON parsing is successful
if (!jsonObj || !jsonObj.CertificateNumber || !jsonObj.CertificateName || !jsonObj.Validation || !jsonObj.Type) {
message.setBody("Error: JSON parsing failed or JSON structure is incorrect.");
return message;
}
// Build HTML table row
def tableRow = """
<tr>
<td>${jsonObj.CertificateNumber}</td>
<td>${jsonObj.CertificateName}</td>
<td>${jsonObj.Validation}</td>
<td>${jsonObj.Type}</td>
</tr>
"""
// Set the HTML body
def htmlBody = """
<html>
<head>
<style>
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}
th {
background-color: #f2f2f2;
}
tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
p {
color: #000000;
font-size: 16px;
}
h2 {
color: orange;
}
</style>
</head>
<body>
<p><i>Hi there!</i></p>
<p><i>This email is a validity notification for the certificate details below. Please review the certificate information.</i></p>
<br>
<h2>Certificate Information</h2>
<table>
<thead>
<tr>
<th>Certificate Number</th>
<th>Certificate Name</th>
<th>Validation</th>
<th>Type</th>
</tr>
</thead>
<tbody>
${tableRow}
</tbody>
</table>
<br>
<p><i>This mail is automatically generated. If you do not receive it properly, please contact your administrator.</i></p>
<br>
<i>Thanks & Regards,</i>
<br>
</body>
</html>
"""
// Set the transformed body
message.setBody(htmlBody);
return message;
}
14-Mail Adapter
Let’s give it a try.
There are two expired certificates.
Overview--> Manage Keystore
Deployed flow and Let's check the keystore again.
Mail:
I sent the emails based on the updated certificates. If you want, you can use gather to consolidate them into a single email format.
If no certificates to be updated are found, the flow is escalated.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
3 | |
3 | |
3 | |
3 | |
2 | |
2 | |
2 | |
2 | |
1 | |
1 |