Skip to Content
Technical Articles

Multi-tenancy of destination service with cloud foundry applications

Introduction:

 

Hi Community,

The purpose of this blog is to share my learnings of cloud foundry destination service. I searched for blogs on multi-tenancy in cloud foundry destination service. I did not find one or I did not search enough😉. So i did some further investigation and experimentation in this area and want to share my findings and learnings so that others looking for similar information can benefit out of it.

As we know, cloud foundry destination service is used to store service endpoints and credential details in a secure way that applications can use during runtime to connect to the services. Destination service is one of the service that inherently supports multi-tenancy. Let us understand multi-tenancy of destination service below.

 

xs-security.json:

 

Multitenancy logically starts at security as tenant level isolation is needed for access to the application. So in xs-security.json file “tenant-mode” is defined as “shared” and a scope “$XSAPPNAME.Callback” is defined for application to access saas-registry.

 

{
    "xsappname": "cfdestination",
    "tenant-mode": "shared",
    "description": "Security profile of cfdestination app",
    "scopes": [{
        "name": "$XSAPPNAME.Callback",
        "description": "With this scope set, the callbacks for tenant onboarding, offboarding and getDependencies can be called.",
        "grant-as-authority-to-apps": [
            "$XSAPPNAME(application,sap-provisioning,tenant-onboarding)"
        ]
    },
    {
        "name": "$XSAPPNAME.read",
        "description": "Read Scope"
    },
    {
        "name": "uaa.user",
        "description": "uaa.user"
    }
    ],
    "role-templates": [
        { 
            "name":"MultitenancyCallbackRoleTemplate",
            "description":"Call callback-services of applications",
            "scope-references":[ 
               "$XSAPPNAME.Callback"
            ]
        },
        {
            "name": "Viewer",
            "description": "Viewer Role Template",
            "scope-references": [
                "$XSAPPNAME.read",
                "uaa.user"
            ]
        }
    ],
    "role-collections": [
        {
            "name": "MyDestinationApplicationViewer",
            "description": "My Destination Application Viewer",
            "role-template-references": [
                "$XSAPPNAME.Viewer",
                "$XSAPPNAME.MultitenancyCallbackRoleTemplate"
            ]
        }
    ]
}

 

Implement rest endpoints for saas-registry:

 

Develop Node.js application with express and implement following endpoints for saas-registry

/callback/v1.0/tenants/*

For tenant onboarding and offboarding.

/callback/v1.0/dependencies

For dependencies

 

Endpoint implementations:

router.get('/callback/v1.0/dependencies', dependencyCallbackRoute);
router.put('/callback/v1.0/tenants/*', subscribeTenantRoute);
router.delete('/callback/v1.0/tenants/*', unsubscribeTenantRoute);

export async function dependencyCallbackRoute(req: Request, res: Response) {
        var dest = xsenv.getServices({ destination: { tag: 'destination' } }).destination;
        var xsappname = dest.xsappname;
        res.status(200).json([{'appId': xsappname, 'appName': 'destination'}]);
}

export async function subscribeTenantRoute(req: Request, res: Response) {
    var consumerSubdomain = req.params["0"];
    var tenantAppURL = "https:\/\/" + consumerSubdomain + "-cfdestinationui.cfapps.eu10.hana.ondemand.com";
    res.status(200).send(tenantAppURL);
}

export async function unsubscribeTenantRoute(req: Request, res: Response) {
    res.status(200);
}

 

Implement endpoint for retrieving destination details:

 

Implement a custom endpoint that provides the destination details for the given destination name. SAP Cloud SDK getDestination() is used to fetch the cloud foundry destinations.

 

import { getDestination, DestinationOptions, DestinationSelectionStrategies } from '@sap-cloud-sdk/core';

router.get("/api/cloudfoundry/destinations", getCFDestinationsRoute);

let options: DestinationOptions = {}

export async function getCFDestinationsRoute(req: Request, res: Response) {
    var paramdestination = req.query.destination;
    options.selectionStrategy = DestinationSelectionStrategies.subscriberFirst;
    options.userJwt = req.authInfo.getTokenInfo().getTokenValue();
    var destination = await getDestination(paramdestination, options);
    if (req.authInfo !== undefined && req.authInfo.checkLocalScope("read")) {
        res.status(200).send(destination.originalProperties);
    } else {
        res.type("text/plain").status(401).send(`ERROR: Not Authorized. Missing Necessary scope`)
    }
    
}

 

Implement Approuter:

 

Implement approuter that acts as single entry point to the application and takes care of user authentication.

 

{
  "name": "cfdestinationui",
  "version": "1.0.0",
  "description": "UI Module for Destination Application",
  "devDependencies": {
    "@sap/grunt-sapui5module-bestpractice-build": "^0.0.14"
  },
  "dependencies": {
    "@sap/approuter": "2.7.1"
  },
  "scripts": {
    "start": "node node_modules/@sap/approuter/approuter.js"
  }
}

 

Configure approuter in xs-app.json file as below:

 

{
    "welcomeFile": "/cfdestinationui/index.html",
    "authenticationMethod": "route",
    "logout": {
        "logoutEndpoint": "/logout"
    },
    "routes": [{
        "source": "^/cfdestinationui/(.*)$",
        "target": "$1",
        "localDir": "webapp"
    }, {
        "source": "^/cfdestinationbackend/(.*)$",
        "target": "$1",
        "csrfProtection": true,
        "authenticationType": "xsuaa",
        "destination": "cfdestinationbackend_api"
    }
]
}

 

Multi Target Application:

 

The application is built as multi target application with an mta.yaml file. It has two modules. cfdestinationbackend, a Node.js application and cfdestinationui, an approuter application. While other parts of mta.yaml is quite standard as with any mta deployments, I request your attention to  section TENANT_HOST_PATTERN which will help to identify the tenant information from the application url.

 

ID: cfdestinationapp
_schema-version: '2.1'
description: A multi-tenant enabled app for cf destination
version: 1.0.0

modules:

  - name: cfdestinationbackend
    type: nodejs
    path: cfdestinationbackend
    parameters:
      disk-quota: 512M
      memory: 512M
    provides:
      - name: cfdestinationbackend_api
        properties:
          backend_app_url: '${default-url}'
    requires:
      - name: cfdestination-uaa
      - name: cfdestination-destination
    properties:
      SAP_JWT_TRUST_ACL:
       - clientid: "*"
         identityzone: "*"

  - name: cfdestinationui
    type: html5
    path: cfdestinationui
    parameters:
      disk-quota: 512M
      memory: 512M
    build-parameters:
      builder: grunt
    requires:
      - name: cfdestination-uaa
      - name: cfdestinationbackend_api
        group: destinations
        properties:
          name: cfdestinationbackend_api
          url: '~{backend_app_url}'
          forwardAuthToken: true
    properties:
      TENANT_HOST_PATTERN: "^(.*)-cfdestinationui.cfapps.eu10.hana.ondemand.com"

resources:
  - name: cfdestination-uaa
    parameters:
      path: ./xs-security.json
      service-plan: application
    type: com.sap.xs.uaa

  - name: cfdestination-destination
    type: org.cloudfoundry.managed-service
    parameters:
      service: destination
      service-plan: lite

 

Deploy the Application to Cloud Foundry:

 

Once deployed, the below service instances created, and applications deployed.

 

Service Instances:

 

Applications:

 

SaaS registry:

 

Configuration file for saas-registry service instance creation is shown below:

 

{
    "appId": "cfdestination!t50911",
    "displayName": "CF Destination App",
    "description": "App to Demo CF Destination",
    "category": "Demo Provider",
    "appUrls": {
        "onSubscription": "https://13fa8802trial-dev-cfdestinationbackend.cfapps.eu10.hana.ondemand.com/callback/v1.0/tenants/{tenantId}",
        "getDependencies": "https://13fa8802trial-dev-cfdestinationbackend.cfapps.eu10.hana.ondemand.com/callback/v1.0/dependencies"
    }
}

 

Get XSAPPNAME from VCAP_SERVICES.xsuaa.credentials.xsappname (here cfdestination!t50911)

Get the onSubscription and getDependencies URL from backend application.

 

Create Saas registry service instance:

cf cs saas-registry application cf-destination-registry -c config.json

 

Bind the backend app to the SaaS registry service instance:

cf bs cfdestinationbackend cf-destination-registry

 

Re-stage the app by executing this command:

cf restage cfdestinationbackend

 

Testing in Provider Tenant:

 

Create destination named “mydestination” at subaccount level as shown

 

 

Calling the API with destination name (/api/cloudfoundry/destinations?destination=mydestination) retrieves the details of destination.

 

 

Now a destination with same name “mydestination” is created in the Destination service instance.

 

 

Calling the API (/api/cloudfoundry/destinations?destination=mydestination) retrieves the details of destination from destination service instance. The SAP Cloud SDK gives preference to the destination in service instance over subaccount and provides the destination details form service instance.

 

 

Onboard a subscriber tenant:

 

Create a Cloud Foundry subaccount for the application consumer (tenant).

 

 

Now additional subaccount just created is seen in the global account screen

 

 

Navigate to the new consumer subaccount and to the Subscriptions tab. The CF Destination App is seen under the Demo Provider category.

 

 

Subscribe to the Application:

 

 

Upon successful subscription, the status shows subscribed:

 

 

Create a new route for the newly subscribed tenant and assign to the approuter/ ui application:

 

 

 

Subscriber subaccount Test:

 

Access the application using subaccount specific route

https://13fa8802mytenant1-cfdestinationui.cfapps.eu10.hana.ondemand.com

Once login, call the api endpoint

https://13fa8802mytenant1-cfdestinationui.cfapps.eu10.hana.ondemand.com/cfdestinationbackend/api/cloudfoundry/destinations?destination=mydestination

Accessing the API will still give the destination details from the destination service instance.

 

 

 

Now create a destination in subscriber subaccount “mytenant1” with same name “mydestination”.

 

 

Calling the api again, will provide the destination details of “mytenant1” subaccount. The SAP Cloud SDK gives preference to subscriber subaccount destinations over provider subaccount destinations.

 

 

Conclusion:

Destination service of cloud foundry is a multitenant service and the multitenant capabilities can be leveraged with sap cloud sdk. The sample application shown in this blog is shared in github repo.

https://github.com/ravipativenu/cf-destinations

There are other blogs on working with destination service in CAP and Node.js and I provided some great references in the references section. I hope you got as much fun reading this blog as I did writing this. I would love to hear your comments and feedback.

Thank you and have a great day…

 

References:

Use SCP CF Destination service in NodeJs locally by Wouter Lemaire

https://blogs.sap.com/2020/05/28/use-the-destination-service-in-nodejs-locally/

CAP: Consume External Service – Part 1 by Jhodel Cailan

https://blogs.sap.com/2020/05/26/cap-consume-external-service-part-1/

Call SAP Cloud Platform destinations from your Node.js application by Maria Trinidad MARTINEZ GEA

https://blogs.sap.com/2018/10/16/call-sap-cloud-platform-cloud-foundry-destinations-from-your-node.js-application/

Consuming destinations in cloud foundry using axios in a nodejs application by Joachim Van Praet

https://blogs.sap.com/2019/11/13/consuming-destinations-in-cloud-foundry-using-axios-in-a-nodejs-application/

Multitenancy Architecture on SAP Cloud Platform, Cloud Foundry environment by Jan Rumig

https://blogs.sap.com/2018/09/26/multitenancy-architecture-on-sap-cloud-platform-cloud-foundry-environment/

Using SaaS Provisioning Service to develop Multitenant application on SAP Cloud Platform, Cloud Foundry Environment by SANDEEP TDS

https://blogs.sap.com/2018/10/25/using-saas-registry-to-develop-multitenant-application-on-sap-cloud-platform-cloud-foundry-environment/

Developing Multitenant Applications on SAP Cloud Platform, Cloud Foundry environment by Hariprasauth R

https://blogs.sap.com/2018/09/17/developing-multitenant-applications-on-sap-cloud-platform-cloud-foundry-environment/

SAP Cloud SDK for JS

https://github.com/SAP/cloud-sdk

 

 

 

 

Be the first to leave a comment
You must be Logged on to comment or reply to a post.