In my last blog post, I’ve written about how I prepared an existing app that was already running on SAP BTP, Cloud Foundry to be deployed on SAP BTP, Kyma runtime. Along the way, I got to know and appreciate some new tools like helm/paketo and kubectl and the entire Kubernetes ecosphere which is quite eye-opening. I had some “touch barrier” for a long time, which may be why it took me a while to dive into the topic more deeply.
Jan Schaffner‘s recent post on LinkedIn drew my attention to the Transparent Proxy for Kubernetes. As usual, I had to take a closer look at the topic and try it out myself to really understand what it is and how it works.
The Transparent Proxy is a software component designed for Kubernetes, which means it can also be used with SAP BTP, Kyma runtime. This tool simplifies the process of calling SAP BTP Destinations for developers. I was impressed with how the Transparent Proxy seamlessly integrates with SAP BTP’s, Kyma runtime, and it definitely exceeded my expectations in terms of the benefits it offers.
Before diving into the steps for using the Transparent Proxy and its inner workings, I think it’s important to first discuss SAP BTP Destinations in general. While these destinations are incredibly useful, they can also cause challenges if you’re not using the correct libraries and SDKs. Luckily, SAP provides various SDKs and frameworks (spoiler: but not for all languages) that make using Destinations on SAP BTP a lot easier. Throughout this process, we’ll explore the existing SDKs and frameworks by SAP and how they help us in using Destinations.
It’s about CAP because it’s not about CAP
You’re right, it may seem like this blog post isn’t directly related to CAP but once you’ve chosen a blog series title (#CAPTricks), you have to stick to it, right? 😉 However, that’s only half of the truth. In this post, I intentionally set aside CAP code to focus on the real problem that the Transparent Proxy solves. Additionally, by the end of the post, readers will hopefully see (again) how CAP simplifies the use of SAP BTP Destination service as a whole.
To provide some context, CAP aims to simplify many essential and repetitive processes. According to the CAP documentation, it can significantly accelerate development, minimize boilerplate code, and improve overall quality by providing single points to fix and optimize. One such process is connecting to remote services or making API calls, which is precisely what this blog post will cover.
In SAP Business Technology Platform (SAP BTP), there’s a service called Destination service that stores information about remote services.
Before I’ll explain the concept of the Destination service, how about asking the most prominent assistant these days?
ChatGPT, what’s the purpose of SAP BTP Destinations?
The Destination service in SAP BTP provides a centralized way to store information about remote services, including the URL, port number, HTTP headers, and authentication information. This approach offers a significant advantage by allowing updates to the Destination information on SAP BTP, rather than modifying the service information in every single application that wants to connect. This saves time and effort and ensures that all applications are updated at once, resulting in increased quality and minimized errors.
As the SAP BTP Destination service is part of your SAP BTP sub-account, not everyone can connect to it. To use the information about a particular Destination in your application, you first need to go through OAuth2 authentication to retrieve the information from the SAP BTP service. While constructing the subsequent call to the actual remote service is not difficult, it can still be challenging, particularly if the actual API is also secured by OAuth2 or if the API is an on-premise system behind an SAP Cloud Connector or SAP Private Link service. Additionally, token handling, such as refreshing them, is a common task that needs to be tackled.
To handle these challenges, several SDKs and frameworks offer support for Destination handling, including the SAP Cloud SDK and the SAP Cloud Application Programming Model (CAP). CAP probably handles Destinations in the most convenient way.
We’ll have a look at different scenarios peu a peu with more help of SDKs or services, but the common ground for all the following paragraphs is the Destination service on SAP BTP. We’ll not going to move this out of this scenario. I’ll only provide the configuration & code examples for Node.js for the sake of not bloating up this blog post. If you closely look at the endpoint we are using for the example code: Yes, it’s basically an OData endpoint that I’m treating as a plain REST endpoint. Also, the examples below do not contain any kind of authentication or SAP Cloud Connector which makes the scenario by far more complex (and realistic, to be honest but less readable in this blog post).
This is the destination we’ll be using throughout this blog post:
Destination usage with plain Node.js with @sap/xsenv
When connecting to a remote service through the SAP BTP Destination service, you need to provide your service instance information, including service credentials, to your application. This can be achieved through various methods:
- By providing environment variables using .env / default-env.json files for local development
- On SAP BTP, Cloud Foundry runtime: Either via the SAP BTP Cockpit, the CF CLI or in the MTA descriptor
- on SAP BTP, Kyma runtime: Via ServiceInstance and a ServiceBinding
This injects the service information (where and how to reach the SAP BTP Destination service and the SAP Connectivity service) either as an environment variable (in case of local development and SAP BTP, Cloud Foundry runtime) or as an mounted Volume for the respective runtime. CAP itself knows how to grab that information and uses it to connect to the SAP BTP Destination service make the subsequent call to the actual remote service.
In the following code, the only SAP-provided helper is the @sap/xsenv package. This package serves as a “utility for easily reading application configurations for bound services and certificates in the SAP BTP, Cloud Foundry environment, SAP XS advanced model, and Kubernetes (K8S)“. It is used to read environment variables based on the three options mentioned earlier in the text, enabling the application to retrieve the service instance information necessary to connect to the SAP BTP Destination service and call the Destination itself. The package reads environments variables based on the three above provided options.
const request = require("request-promise");
const env = require("@sap/xsenv");
env.loadEnv();
const destinationService = env.getServices({
dest: { tag: "destination" },
}).dest;
const xsuaaService = env.getServices({ uaa: { tag: "xsuaa" } }).uaa;
const xsuaaCreds = `${destinationService.clientid}:${destinationService.clientsecret}`;
const destinationName = "northwind";
async function getNorthwindExampleData() {
try {
const tokenResponse = await request({
uri: `${xsuaaService.url}/oauth/token`,
method: "POST",
headers: {
Authorization: `Basic ${Buffer.from(xsuaaCreds).toString("base64")}`,
"Content-type": "application/x-www-form-urlencoded",
},
form: {
client_id: destinationService.clientid,
grant_type: "client_credentials",
},
});
const token = JSON.parse(tokenResponse).access_token;
const destinationResponse = await request({
uri: `${destinationService.uri}/destination-configuration/v1/destinations/${destinationName}`,
headers: {
Authorization: `Bearer ${token}`,
},
});
const oDestination = JSON.parse(destinationResponse);
const result = await request({
method: "GET",
uri: `${oDestination.destinationConfiguration.URL}Employees?$top=10&$select=FirstName`,
});
As you may have noticed, calling the SAP BTP Destination service yourself, including authentication and token handling, and then making the call to the actual remote service can be quite challenging. In the example shown above, the remote service is not secured, making it relatively simple. However, handling token refresh and managing authentication makes the flow more complex. As you can see and most likely know or can even imagine: quite some effort and headache.
If you want to know more about it, Carlos Roggan has written a blog post about the details of calling an on-premise API using plain Node.js How to call – onPrem System – from Node.js app – via Cloud Connector.
Destination usage with the SAP Cloud SDK for JavaScript
If we want to avoid the headaches and problems associated with the manual handling of SAP BTP Destination service authentication and remote service calls, one option is to use the SAP Cloud SDK for JavaScript. According to the SDK’s landing page on https://sap.github.io/cloud-sdk/: “The SAP Cloud SDK for JavaScript helps you build cloud-based apps and extensions to SAP solutions using the power and flexibility of Node.js and its ecosystem.”
While the application still requires service credentials for the SAP BTP Destination service, the SDK makes connecting to the service and calling the remote service much more convenient and robust, with all the necessary handling taken care of.
Below is an example of how to use the SAP Cloud SDK for JavaScript to connect to a remote service.
import { executeHttpRequest } from '@sap-cloud-sdk/http-client';
import xsenv from '@sap/xsenv';
xsenv.loadEnv();
const destinationName = 'northwind';
async function getNorthwindExampleData() {
const result = await executeHttpRequest(
{
destinationName: destinationName,
},
{
url: 'Employees?$top=10&$select=FirstName',
},
{
fetchCsrfToken: false,
}
}
Destination usage with SAP Cloud Application Programming Model (CAP) – Node.js & Java
Next, let’s look at how to use SAP Cloud Application Programming Model (CAP) to call a remote service using Destinations. In essence, calling the remote service with CAP is not much different from using the SAP Cloud SDK, especially when it comes to the example request we are trying to accomplish here. You can read more about the similarities and differences between the two in Milton Chandradas‘s blog post: SAP Cloud SDK and its relationship with SAP Cloud Application Programming Model.
One advantage of using CAP is that the CDS cli provides additional convenience, such as the ability to mock remote services or a protocol agnostic query language like CQL. Another benefit is that you can provide the necessary service binding for local development using cds bind, which eliminates the need for manual configuration.
When it comes to configuring Destinations with CAP, there isn’t much that needs to be done. You simply need to pass the Destination name to cds and call the remote service in your custom handlers.
cds configuration in package.json or cdsrc.json:
{
"cds": {
"requires": {
"MyRemoteService": {
"kind": "rest",
"credentials": {
"destination": "northwind"
}
}
}
}
}
With that you can simply connect to the remote service using the following lines of code:
const myRemoteServiceObject = await cds.connect.to('MyRemoteService');
const employees = await myRemoteServiceObject.get('/Employees?$top=10&$select=FirstName');
That’s all. CAP takes care of the details about how to connect to that service. May it be the specifics of an on-premise system with the detour via the SAP Connectivity Service, or Principal Propagation and so on. CAP handles all of these scenarios for the developer, making it easier to focus on building the application logic instead of worrying about the details of connecting to the remote service.
So, Max, what’s the Transparent Proxy for Kubernetes and why should I use it?
Let’s talk about the Transparent Proxy for Kubernetes and why it’s beneficial to use. I apologize for the detour in explaining how the SAP BTP Destination service is typically used in Node.js or Java (even though I haven’t provided the corresponding samples) projects, but it’s important to understand the sweet spot of the Transparent Proxy.
Consider a scenario where you don’t have access to SDKs or frameworks like CAP for whatever reason. Perhaps you’re using a programming language that doesn’t have such tools available, like Python, Go, Rust, or others. In such a case, wouldn’t it be great if there was a mechanism that could take away the responsibility of handling destination specifics from the developer, using an infrastructure mechanism to “intercept” calls and adjust them based on the configuration of the actual destination on SAP BTP?
The Transparent Proxy for Kubernetes does exactly that. Once the Transparent Proxy is installed in SAP BTP, Kyma runtime, or any Kubernetes cluster, developers only need to consider two key aspects:
A custom Kubernetes resource of kind destination.connectivity.api.sap that you need to apply:
---
apiVersion: destination.connectivity.api.sap/v1
kind: Destination
metadata:
name: tproxydestination
spec:
destinationRef:
name: "northwind"
With that, one can easily make a call to the actual remote service by using the name of the destination resource defined above – TProxyDestination. To better understand it, a curl example beautifully demonstrates it (though escaping might make it a bit less pretty 😉).
curl tproxydestination/Employees?$top=10&$select=FirstName
Impressive, right? However, this was just a basic example using an unsecured public endpoint. The true power of the Transparent Proxy lies in its ability to handle secured remote services protected by OAuth and likely hidden behind a SAP Cloud Connector. By taking over the responsibility of handling destination specifics, the Transparent Proxy saves developers from dealing with cumbersome configurations and adjustments.
How can I use it and how does it work?
If you’re interested in using the Transparent Proxy and want to know how it works, here’s a brief overview. To get started, you’ll need to configure the SAP-provided Helm Chart with information specific to your environment, such as the service details for the SAP BTP Destination service and the location of the Transparent Proxy Docker image. The instructions for setting up the Transparent Proxy with the Repository-Based Shipment Channel (RBSC) can be found on help.sap.com, but let’s take a closer look at the step-by-step setup process.
1. Create a technical user for SAP Repositories Management
2. Create a secret for that technical user in your namespace
kubectl create secret docker-registry docker-secret --docker-server=73554900100900006891.dockersrv.cdn.repositories.cloud.sap --docker-username=<rbsc-user> --docker-password=<password>
3. Create a ServiceInstance and ServiceBinding for the SAP BTP Destination service, either using the SAP BTP, Kyma runtime Dashboard or kubectl:
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: transparent-proxy-dest
spec:
serviceOfferingName: destination
servicePlanName: lite
---
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: transparent-proxy-dest-binding
spec:
serviceInstanceName: transparent-proxy-dest
4. In case you want to use the SAP Cloud Connector, please enable the Connectivity Proxy in your cluster.
5. Configure the necessary values in the values.yaml
file. An example configuration that I have used is shown below, while a comprehensive list of all available properties, including the required ones, can be found on help.sap.com.
config:
integration:
destinationService:
serviceCredentials:
secretName: transparent-proxy-dest-binding
platform:
region: us20
security:
communication:
internal:
encryptionEnabled: false
accessControl:
destinations:
defaultScope: namespaced
deployment:
image:
registry: 73554900100900006891.dockersrv.cdn.repositories.cloud.sap
repository: com.sap.core.connectivity.transparent-proxy
pullSecret: docker-secret
- config.integration.destinationService.serviceCredentials.secretName: refers to the secret that has been created by creating the ServiceBinding resource.
- config.platform.region: refers to the region of your SAP BTP subaccount (regions on help.sap.com)
- deployment.image.registry: refers to the image registry. remains the same for all installations since this is the registry SAP uses to distribute the Transparent Proxy
- deployment.image.pullSecret: refers to the secret name you have created in one of the earlier steps using kubectl
6. Once you’ve completed the previous configuration steps, you’re ready to install the Transparent Proxy in your cluster using Helm. Simply reference the values.yaml file you configured in the earlier steps, and the installation process will use your customized settings.
helm install transparent-proxy transparent-proxy-helm/transparent-proxy --version=1.2.0 --namespace <yournamespace> -f values.yaml
What you get as a result is a set of pods (as part of a Deployment that you can scale using ReplicaSets) that seamlessly manage the entire lifecycle of Destinations as a crucial component of the Transparent Proxy solution.
7. Create a Destination as a custom Kubernetes resource in the SAP BTP, Kyma runtime cluster:
---
apiVersion: destination.connectivity.api.sap/v1
kind: Destination
metadata:
name: tproxydestination
spec:
destinationRef:
name: "northwind"
The Transparent Proxy’s reconciler will detect the Destination resource and generate a Kubernetes Service with the name specified in metadata.name. Once a Kubernetes Service is established, it is allocated a DNS name in the cluster’s DNS service. This DNS name is derived from the Service name and its associated namespace, allowing it to be accessed from within the cluster.
By using the name of a Kubernetes Service to call it, Kubernetes will automatically resolve the DNS name to the Service’s virtual IP address. This results in the request being directed to the appropriate Pod through its load balancing mechanism, and this Pod is one of the Pods created by the Helm chart installation as you can see below.
Takeaway:
Sometimes it’s easy to forget just how complex it can be to call remote services and APIs, especially if you don’t have the right tools at your disposal. While the SAP Cloud SDK and CAP do an excellent job for Node.js and Java, the Transparent Proxy is a great solution for taking the responsibility of low-level API calls away from developers in other languages.
To be very clear: This blog post is not specifically about CAP and the Transparent Proxy is only available for Kubernetes.
If you’re interested in learning more about the goal and features of the Transparent Proxy, I recommend reading Iliyan Videnov‘s blog post: Transparent Consumption of Connectivity.
Slightly off-topic: In my recent blog post, I explored SAP BTP’s, Kyma runtime and its various capabilities, benefits, and challenges. I realized that the world of Kubernetes is extensive and offers far more opportunities for software development than traditional deployment methods like “cf push/cf deploy” in Cloud Foundry. As a result, it can be challenging to compare the two runtimes. However, factors like the need for a service mesh, control over application distribution, and flexibility in tool selection often lead developers to choose SAP BTP’s, Kyma runtime. It’s important to remember that this discussion is not about comparing the two runtimes, but rather explaining the technical topic of the Transparent Proxy for Kubernetes.
If you’re interested in learning more about the similarities and differences between SAP BTP’s, Cloud Foundry and Kyma Runtimes, I highly recommend reading Alper Dedeoglu‘s blog post: Developing an Application on SAP Cloud Foundry Runtime and SAP Kyma Runtime: A Comparative Analysis.