This repository contains a sample reference application for developing and deploying a SaaS (software-as-a-service) multitenant business application on SAP Cloud Platform Cloud Foundry environment. Follow the instructions below to deploy the application on SAP Cloud Platform in a subaccount that is configured for the Cloud Foundry environment. The sample application uses PostgreSQL and Redis as backing services.
As mentioned here:
"Multitenancy refers to a software architecture, in which tenants share the same technical resources, but keep the data separated and identity and access management for each tenant isolated."
A multi-tenant business application provides a suite of functional services to a group of customers. The developer and deployer of the application service (e.g. a company with a Global Account on SAP Cloud Platform) is often referred to as the provider while the customers of the service are referred to as consumers.
We will use the Cloud Foundry CLI for deploying the applications onto the Cloud Foundry landscape. The process can be simplified further into a unified deployment experience using the concept of Multi-Target Archives (MTAs). This is left to the reader as an exercise in order to keep the concerns of deployment separate from the intention of developing a multi-tenant application.
The following diagram illustrates the high-level component architecture for this application:
cf api https://api.cf.eu10.hana.ondemand.com
cf login -u <email_address> -p <password>
cf target -o <org_name> -s <space_name>
cf create-service postgresql <postgres_service_plan> <master_postgres_service_instance_name>
Note: Provisioning of a PostgreSQL instance is asynchronous in nature- please follow the instructions provided in the output for understanding the workflow.
cf create-service redis <redis_service_plan> <service_instance_name_for_redis_instance>
We will use xsuaa
as the business user authentication and authorization service for the multi-tenant application. We will create two service instances of xsuaa
:
SaaS-Provisioning
service to call the tenant-manager
application on onboarding and offboarding requests. The security profile for this instance is defined in this file. Note that the value for the parameter tenant-mode
is shared
and the profile descriptor grants authority to the saas-provisioning
application to call the associated application.xsuaa
instance can be bound to all approuter-service bundles as per need. The security profile for this instance is defined in this file. Note the value for the parameter tenant-mode
is shared
and the profile descriptor defines a set of role templates, scopes and attributes.Run the following commands to create the two xsuaa
instances. Replace the placeholders with appropriate values before running the commands:
cf create-service xsuaa application <xsuaa_service_instance_name> -c security/xs-security-saas-provisioning.json
cf create-service xsuaa application <business_xsuaa_service_instance_name> -c security/xs-security-services.json
Open the manifest.yml
deployment descriptor in the root of this repository. Replace the following placeholders with appropriate values as described below:
<master_postgres_service_instance_name>
: this should be replaced with the name chosen for the tracking PostgreSQL service instance above<service_instance_name_for_redis_instance>
: this should be replaced with the name chosen for the Redis service instance above<xsuaa_service_instance_name>
: this should be replaced with the name chosen for the XS UAA service instance for authorizing SaaS provisioning callbacks<business_xsuaa_service_instance_name>
: this should be replaced with the name chosen for the XS UAA service instance for business service authorizations<route_for_ui_application_without_protocol>
: this should be replaced with the fully qualified URL for the approuter application without the protocol as a prefix (e.g. my-beautiful-approuter-ui.cfapps.eu10.hana.ondemand.com
)<generated_username_for_devops_onboarding>
: this should be replaced with a suitably random string. This is used as the basic authentication username for authorizing pre-onboarding setup. You can choose to create a random string using a utility command like cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1
<generated_password_for_devops_onboarding>
: this should be replaced with a suitably random string. This is used as the basic authentication password for authorizing pre-onboarding setup. You can choose to create a random string using a utility command like cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1
<route_for_tenant_manager_application_without_protocol>
: this should be replaced with the fully qualified URL for the tenant manager application without the protocol as a prefix (e.g. my-tenant-manager.cfapps.eu10.hana.ondemand.com
)<route_for_backend_application_without_protocol>
: this should be replaced with the fully qualified URL for the product backend application without the protocol as a prefix (e.g. my-beautiful-backend.cfapps.eu10.hana.ondemand.com
)<space_developer_user>
: this should be replaced with the username (email address) of the Cloud Foundry user with Space Developer role assigned for the provider space- the username is required for creating and mapping the CF route for the subscribed application at runtime. Note that this step is not needed and must be removed from the codebase in case of a productive scenario where wildcard hostname routing with a custom domain is preferred and recommended (see the section on Tenant Identification at Multi Tenancy Architecture for more details)<space_developer_password>
: this should be replaced with the password of the Cloud Foundry user with Space Developer role assigned for the provider space<cf_uaa_endpoint>
: this should be replaced with the Cloud Foundry UAA Login resource URL, prefixed with the HTTPS protocol (e.g. https://login.cf.eu10.hana.ondemand.com)<cf_api_endpoint>
: this should be replaced with the Cloud Foundry Controller API endpoint URL, prefixed with the HTTPS protocol (e.g. https://api.cf.eu10.hana.ondemand.com)Build the Master PostgreSQL deployer. The module is called database
and deploys some tracking objects into the master PostgreSQL instance's "public" schema.
cd database
mvn clean package
cd ..
target
directory was generated as as result of the Maven build phase and it should contain a JAR file generated out of the buildRun the deployment by issuing the following command from the root of this repository:
cf push
The SAP cloud platform provides a service in the marketplace called saas-registry
and service plan application
. This service is responsible for providing the application as a service to other subaccounts as a subscription. You need to create an instance of saas-registry
with plan application
passing along parameters for the multi-tenant app configurations. Follow the steps below for this:
config.json
in the directory multi-tenant-config
. Replace the placeholders in the file with values below:
<generated_xsappname_for_xsuaa_environment>
: Inspect the xsuaa
service binding for the tenant-manager application using cf env tenant-manager
and copy over the generated value for the field xsappname
<route_for_tenant_manager_application_without_protocol>
: Replace with the route of the tenant manager application as mentioned abovesaas-registry
(application
plan) using the config file mentioned above:
cf create-service saas-registry application saas-provisioning-service -c multi-tenant-config/config.json
tenant-manager
application:
cf bind-service tenant-manager saas-provisioning-service
cf restage tenant-manager
The repository provides two shell scripts inside this directory, which are described in detail below:
preonboarding_admin.sh
: This script is represented in the Component Architecture diagram above. This is expected to be run by the application provider on behalf of a new consumer before the subscription is finalized. The interactive script is responsible for creating a PostgreSQL instance, creating a service key for the instance and calling an admin API which maps the tenant information to the instance and credentials.
migrate_all_consumer_dbs.sh
: This script is expected to be run by the application provider in case there arises a need to run database change management for all the PostgreSQL instances for all consumer accounts. This is typically a lifecycle operation, common during the development phase of a product where changes are frequent and difficult to predict.
The users of the business application would generally be authenticated and stored in a custom Identity Realm, commonly called Identity Providers. This means that there needs to be security trust established between the SAP authorization service (service provider) and the identity provider itself.
The SAP Identity Authentication Service is a cloud service solution for secure authentication and user management in SAP cloud and on-premise applications. It provides a suite of services for authentication, single sign-on, and user management. The service provider's metadata can be downloaded from the consumer subaccount's authentication domain and uploaded to the Identity Authentication service to establish the first leg of trust. The second leg of trust needs to be established using the SAP Cloud Platform Cockpit Trust Configuration UI.
Once the trust configuration and two-way security initiative is set up, the Identity Authentication service can be used by the tenant administrator to set up relevant user groups, define user attributes, etc. The SAP Authorization component (XSUAA) is responsible for intercepting the user relevant information and passing it along to the target business application in an encoded format (JSON Web Token) using standard OAuth 2.0 protocol.