DEV Community

Vipin Vijaykumar
Vipin Vijaykumar

Posted on

Bye-Bye Credentials! Automate BTP & Cloud Foundry Setup with Terraform using Github Actions and Github OIDC

Imagine this: You want to automate setting up SAP BTP Accounts and Cloud Foundry using Terraform, but without the pain of managing and tracking usernames, passwords, or rotating any of those credentials.

Well, that's no longer a dream! Most common CI/CD Solutions like Github/Gitlab actions, Jenkins, Azure DevOps etc and OIDC Provider integration, you can wave goodbye to maintaining long-lived credentials. Say hello to short-lived, secure, just-in-time tokens.

In this blog, we’ll set up GitHub’s OIDC provider to authenticate directly against SAP BTP using JWT Bearer Assertion flow, and then use Terraform to provision your BTP resources, Cloud Foundry spaces, and more. These steps work for both Terraform and OpenTofu.

What's a JWT Bearer Assertion Anyway?

Before we jump into Terraform and GitHub Actions, let’s talk JWT Bearer tokens. A JWT Bearer Assertion is a way for a client to prove its identity to an authorisation server using a signed JSON Web Token (JWT) instead of a password or traditional credentials. Since the tokens are short-lived and ephemeral, they bring somewhat better security and rotation.

In short: Instead of passing around long-term credentials, GitHub can request a short-lived token for your pipeline, signed and trusted via OpenID Connect. This token is accepted by an SAP Cloud Identity Service tenant, which then lets Terraform (on behalf of GitHub Actions) access BTP and Cloud Foundry APIs.

Event Sequence

  • Github Workflow is triggered
  • The Github OIDC Provider is called, and a JWT Token is generated for the workflow. This token is stored in environment variables or github variables
  • The JWT Bearer token is used by the BTP & CF terraform provider in the Terraform scripts
  • The calls for the respective providers are authenticated in BTP and CF (UAA) via the trusted SAP Cloud Identity Service tenant. The bearer token is forwarded to the SAP Cloud Identity Service
  • SAP Cloud Identity services validate the token against the Github OIDC Public Keys(JWKS)
  • If the key is successfully validated, the clients are authenticated, and subsequent calls can be made.

Let’s dig in!

This tutorial builds upon a community blog that showcases how the cf CLI can leverage GitHub OIDC for logging into and managing Cloud Foundry.

We'll reference the community blog for the initial setup steps as it's well-documented there.

Step 1: Establish Trust between SAP BTP & SAP Cloud Identity Services

Please refer to the official documentation for doing the same

Step 2: Create the Github Repo

  • Create the github repo with repository-name in the Organisation you need. This repository and name will be used in the next steps.

Step 3:Configure SAP Cloud Identity Services

In this step, configure the SAP Cloud Identity Services to

  • Add the Repo User for Github Action Workflows. Note:
    • Name format: repo/<Organisation>/<repository-name>
    • Email format: repo/<Organisation>/<repository-name>@<github-domain>
  • Add the Repo User to a group
  • Configure Github OIDC as a Corporate IdP
  • Map the repository name as NameId (Enriched Token Claims)
  • Configure Identity Federation.
  • Set up the BTP Application to use the GitHub IDP

Please refer to the community blog mentioned above for the detailed steps with regards to this.

Step 4: Add user to Global Account

  • Add the repo-user email, created in SAP Cloud Identity Services in BTP.

  • Since we will be working with creating a subaccount and managing resources at the Global Account level, assign the Global Account Administrator Role Collection.

Step 5: Add content to the Github Repository

  • The following will be the structure of the project and files.
<repository-root-directory>/
├── .github
│   └── workflows
│       └── deploy.yml
├── main.tf
├── provider.tf
├── terraform.tfvars
└── variables.tf
Enter fullscreen mode Exit fullscreen mode
  • Here we create a Terraform configuration that uses the Terraform providers for SAP BTP & Cloud Foundry to:-

  • Add the configuration to initialise the provider.tf file

main.tf

The main.tf has the definition of the infrastructure that will be created. For this article, we take a simple scenario as:-

  • Create a Subaccount
  • Initialise a Cloud Foundry Runtime Environment in the subaccount
  • Create a space within Cloud Foundry

This would, of course vary depending on your setup and scenario. So we won't delve into this section much.

For details on the different resources & datasources present in the Terraform providers for SAP BTP & Cloud Foundry, please refer to the registry.

resource "btp_subaccount" "project" {
  name      = "sample project"
  subdomain = "sample.project"
  region    = lower(var.region)
}

resource "btp_subaccount_environment_instance" "cloudfoundry" {
  subaccount_id    = btp_subaccount.project.id
  name             = "sample-cf-instance"
  landscape_label  = "cf-ap10"
  environment_type = "cloudfoundry"
  service_name     = "cloudfoundry"
  plan_name        = "standard"
  parameters = jsonencode({
    instance_name = "sample-cf-instance"
  })
}

resource "cloudfoundry_space" "space" {
  name = "dev_space"
  org  = btp_subaccount_environment_instance.cloudfoundry.platform_id
}
Enter fullscreen mode Exit fullscreen mode

variables.tf file

Here we declare the variables globalaccount and idp. You'd have more depending on your script.

variable "globalaccount" {
  type        = string
  description = "The subdomain of the SAP BTP global account."
}

variable "idp" {
  type        = string
  description = "Orgin key of Identity Provider"
  default     = null
}
Enter fullscreen mode Exit fullscreen mode

Configure provider.tf

By convention, the provider.tf file usually represents the configuration to initialise the providers. Here we will configure the SAP BTP and Cloud Foundry providers.

terraform {
  required_providers {
    btp = {
      source  = "sap/btp"
      version = "~> 1.15.0"
    }
    cloudfoundry = {
      source  = "cloudfoundry/cloudfoundry"
      version = "~> 1.8.0"
    }
  }
}

provider "btp" {
  globalaccount = var.globalaccount
  idp           = var.idp
}
provider "cloudfoundry" {
  api_url = "https://api.cf.${var.region}.hana.ondemand.com"
  origin  = var.idp
}
Enter fullscreen mode Exit fullscreen mode

As can be seen:-

  • btp provider is configured with the global account and the idp (Based on the trust configured in the above steps)
  • cloudfoundry is configured with the cf-api and the origin(uaa), which is the same trust configuration as created above

  • Since the credentials used here is the assertion token, we'll be using the environment variables BTP_ASSERTION and CF_ASSERTION_TOKEN.

Note:- CF API depends on the region where the subaccount is created. Please refer to the right API in the official document

Step 6: Add the Github Workflow

  • The Github workflow initialises Terraform, generates the token and stores it as Github Outputs. These tokens are then assigned as environment variables to the Terraform Providers. Thereafter, the Terraform executes the scripts just as any typical workflow.
name: Deploy
on:
  workflow_dispatch:
    inputs:
      region:
        description: 'BTP Subaccount Region'
        required: true
        type: string
      idp:
        description: 'Identity Provider'
        required: true
        type: string
      global_account_id:
        description: 'Global Account ID'
        required: true
        type: string
      tenantIssuerUri:
        description: 'Issuer URL'
        required: true
        type: string
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
        id-token: write # This is required for requesting the JWT
        contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_wrapper: false
          terraform_version: latest

      - name: Install OIDC Client from Core Package
        run: npm install @actions/core @actions/http-client

      - name: Get Id Token
        uses: actions/github-script@v7
        id: idtoken
        env:
          TENANT_URL: ${{inputs.tenantIssuerUri}}
        with:
          script: |
            const tenanturl = process.env.TENANT_URL;
            const coredemo = require('@actions/core')
            let id_token = await coredemo.getIDToken(tenanturl)
            coredemo.setOutput('githubJwt', id_token)

      - name: Apply Terraform configuration
        run: |
          export BTP_ASSERTION="${{ steps.idtoken.outputs.githubJwt }}"
          export CF_ASSERTION_TOKEN="${{ steps.idtoken.outputs.githubJwt }}"
          terraform apply -auto-approve -var "region=${{ inputs.region }}" -var "idp=${{ inputs.idp }}" -var "globalaccount=${{ inputs.global_account_id }}"
Enter fullscreen mode Exit fullscreen mode

For this article, the workflow is triggered manually based on user inputs. It takes the following inputs:-

  • BTP Subaccount Region: Region where the Subaccount will be created
  • Identity Provider: The name of the identity provider (Origin Key (Cloud Foundry)) that was configured in the Trust Configuration in BTP from the above step.
  • Global Account ID: The Global Account Subdomain
  • Issuer URL: The URL of the SAP Cloud Identity Service Tenant. Github OIDC Provider issues tokens for this tenant as the audience.

The token is generated in the step Get Id Token. In this article, we use the core package for the GH Actions Toolkit. An alternative approach of using curl. This approach can be seen in the blog referred above.

Step 7: Trigger Workflow

Trigger the workflow from the Github Actions tab. Add the necessary details as seen below.

The Job should run successfully, whereby you can see that the Token is generated and used by the subsequent steps.

Conclusion

By federating GitHub Actions with SAP Cloud Identity and leveraging the JWT Bearer Assertion flow, you can automate provisioning of SAP BTP resources and Cloud Foundry environments without storing or rotating long-lived service credentials.

Some Best Practices

  • Use any supported remote, encrypted Terraform/OpenTofu backends like HCP, Vault, OpenBao, S3/GCS/Azure Blob, etc, with server-side encryption.
  • Work with least privilege & role collections: In BTP, assign the repo user (or group) only the role collections needed for the automation (For e.g Avoid Global Account Admin if the setup is for a specific subaccount).
  • Ensure GitHub Repo is protected and require reviewers for workflows that can create or alter infrastructure.
  • Test your workflow setup on dev/test environments first.

Top comments (0)

The discussion has been locked. New comments can't be added.