Creating roles for your CAP Application on SAP BTP Kyma Runtime

When you deploy a SAP CAP application to Kyma, it usually requires authentication and authorization. This is typically handled by an XSUAA instance, which provides OAuth2-based authentication and role-based access control (RBAC). In this blog post, we will explore how to create roles for your CAP application on SAP BTP Kyma Runtime, and how to assign them to users and groups.

Series — SAP BTP Kyma Runtime:

  1. Preparing Your Environment For SAP BTP Development on Kyma Runtime
  2. Deploying Your First CAP Application on SAP BTP Kyma Runtime
  3. Creating BTP Service Instances on SAP BTP Kyma Runtime with the Service Operator
  4. Binding BTP Service Instances to your CAP Application with Kyma Runtime
  5. The Chart Templates Behind Your CAP Kyma Deployment
  6. Creating roles for your CAP Application on SAP BTP Kyma Runtime (this post)
  7. Using Istio Gateway as Entry Point in SAP BTP Kyma Runtime

Creating the CAP application

We will start with the same configuration as in the previous blog post, where we created a sample CAP application bound to some BTP service instances, including an XSUAA instance. For details review the previous post. Now execute the following commands to create a new CAP application:

Copy
cds init my-cap-app
cd my-cap-app
cds add nodejs
npm install
cds add tiny-sample
cds add hana xsuaa
cds add kyma

Configuring scopes and roles in the XSUAA instance

We will focus now in the /chart directory and the files in there. The values.yaml file contains the configuration for the XSUAA instance:

Copy
...
xsuaa:
  serviceOfferingName: xsuaa
  servicePlanName: application
  parameters:
    tenant-mode: dedicated
    oauth2-configuration:
      redirect-uris:
        - https://*.{{ tpl .Values.global.domain . }}/**
    xsappname: my-cap-app-{{ .Release.Namespace }}
...

In it we will add the scopes, roles and role-collections sections to define the scopes and roles for our application. For example, we can add the following configuration:

Copy
xsuaa:
  serviceOfferingName: xsuaa
  servicePlanName: application
  parameters:
    tenant-mode: dedicated
    oauth2-configuration:
      redirect-uris:
        - https://*.{{ tpl .Values.global.domain . }}/**
    xsappname: my-cap-app-{{ .Release.Namespace }}
    scopes:
      - name: "$XSAPPNAME.reader"v. 
        description: Reader scope for my-cap-app in kyma
      - name: "$XSAPPNAME.admin"
        description: Admin scope for my-cap-app in kyma
    role-templates:
      - name: reader-role
        description: Reader role for my-cap-app in kyma
        scope-references:
          - "$XSAPPNAME.reader"
      - name: admin-role
        description: Admin role for my-cap-app in kyma
        scope-references:
          - "$XSAPPNAME.admin"
    role-collections:
      - name: reader-role-collection
        description: Reader role collection for my-cap-app in kyma
        role-template-references:
          - "$XSAPPNAME.reader-role"
      - name: admin-role-collection
        description: Admin role collection for my-cap-app in kyma
        role-template-references:
          - "$XSAPPNAME.admin-role"

Using the roles in your CAP application

Now that we have defined the scopes and roles in the XSUAA instance, we can use them in our CAP application. In the srv directory, we should edit the cat-service.cds file to add the @requires annotation to the service definition. For example, we can add the following annotation to require the read scope for the CatalogService:

Copy
@odata
annotate CatalogService with @(restrict: [
    { grant: ['READ'], to: 'reader' },
    { grant: ['READ', 'WRITE'], to: 'admin' }
]);
service CatalogService {
  entity Books {
    key ID     : Integer;
        title  : String;
        author : String;
  }
}

Deploy the application to Kyma

Finally, we can deploy the application to Kyma using the following command:

Copy
cds up --to k8s -namespace my-namespace

For more details on how to deploy your CAP application to Kyma, please refer to the previous blog post.

Assigning roles to users and groups

Once the application is deployed, the roles and role collections are available in the BTP cockpit. We can go to Role Collections and assign the reader-role-collection to users or groups.

SAP BTP Cockpit security role collections

Select one of the roles collection and add your user in the Users section.

Testing the roles

To test the roles, we need to use an entry point that authenticates the user and provides the access token with the assigned roles. In this case, we can use an Approuter application that we can bind to the XSUAA instance and proxy the requests to the CAP application.

To do so run the following command:

Copy
cds add approuter

This create a new folder /app/router with the approuter application. To know more about the approuter application, please refer to the official documentation.

In this case the file we are interested in is the xs-app.json file, which defines the routes and authentication methods for the application. Modify its content to set the authentication method for the route:

Copy
{
  "authenticationMethod": "route",
  "routes": [
    {
      "source": "^/(.*)$",
      "target": "$1",
      "destination": "srv-api",
      "csrfProtection": true,
      "authenticationType": "xsuaa"
    }
  ]
}

The previous script also added the Approuter image description in the containerize.yaml file:

Copy
# containerize.yaml
...
  - name: my-cap-app-approuter
    build-parameters:
      buildpack:
        type: nodejs
        builder: builder-jammy-base
        path: app/router
        env:
          BP_NODE_RUN_SCRIPTS: ""

The chart dependency in the Chart.yaml file:

Copy
# Chart.yaml
apiVersion: v2
name: my-cap-app
description: A simple CAP project.
type: application
version: 1.0.0
appVersion: 1.0.0
annotations:
  app.kubernetes.io/managed-by: cds-dk/helm
dependencies:
  ...
  - name: web-application # The approuter application dependency
    alias: approuter
    version: ">0.0.0"

And the values for the Approuter application in the values.yaml file:

Copy
# values.yaml
...

srv:
  bindings:
    auth:
      serviceInstanceName: xsuaa
    db:
      serviceInstanceName: hana
    destination:
      serviceInstanceName: destination
  image:
    repository: my-cap-app-srv
  resources:
    limits:
      ephemeral-storage: 1G
      memory: 500M
    requests:
      ephemeral-storage: 1G
      cpu: 500m
      memory: 500M
  health:
    liveness:
      path: /health
    readiness:
      path: /health
  networkSecurity:
    allowNamespaceInternal: true

...

approuter:
  image:
    repository: my-cap-app-approuter
  resources:
    limits:
      ephemeral-storage: 1G
      memory: 500M
    requests:
      ephemeral-storage: 1G
      cpu: 500m
      memory: 500M
  health:
    liveness:
      path: /
    readiness:
      path: /
  envFrom:
    - configMapRef:
        name: "{{ .Release.Name }}-approuter-configmap"
backendDestinations:
  srv-api:
    service: srv
    Authentication: OAuth2UserTokenExchange

In the approuter values section, we need to add the binding to the XSUAA instance with the following code:

Copy
approuter:
  bindings: # Add the binding to the XSUAA instance
    auth:
      serviceInstanceName: xsuaa
  image:
    repository: my-cap-app-approuter
  resources:
    limits:
      ephemeral-storage: 1G
      memory: 500M
    requests:
      ephemeral-storage: 1G
      cpu: 500m
      memory: 500M
  health:
    liveness:
      path: /
    readiness:
      path: /
  envFrom:
    - configMapRef:
        name: "{{ .Release.Name }}-approuter-configmap"

Note that the backendDestinations section defines the srv-api destination for the CAP application, which points to the srv service. The Authentication property is set to OAuth2UserTokenExchange, which means that the Approuter will exchange the user token for a token that can be used to access the CAP application.

So what it does is creating a destination named srv-api that points to the host of the CAP application (which will be defined during the deployment). When the user access the Approuter, it will authenticate the user and get the access token with the assigned roles. Then it will forward the request to the CAP application with the access token in the Authorization header.

Let's now deploy the application again:

Copy
cds up --to k8s -namespace my-namespace

Once the deployment is complete, we should see 2 services available:

Approuter deployment complete

When accessing the Approuter URL, it will first redirect to the XSUAA login page, and after logging in, we will be redirected back to the Approuter, which will forward the request to the CAP application with the access token.

Let's access the Approuter URL in a browser, pointing to the catalog service of the CAP application /odata/v4/catalog/Books:

Copy
https://my-cap-app-approuter-my-namespace.<my-kyma-subdomain>.kyma.ondemand.com/odata/v4/catalog/Books

If the role collections were not assigned to the user, we will get a 403 Forbidden error. If the user has the reader-role-collection assigned, we will be able to access the catalog service and see the list of books.

403 Forbidden error

After assigning the role collection as described in the previous section, we can refresh the page and see the list of books:

Catalog service