DEV Community

Mohammed Ammer
Mohammed Ammer

Posted on

Custom Identity Providers in Keycloak with Terraform: A Step-by-Step Guide

Introduction

In this blog post, we will be discussing a Terraform code block for creating an custom identity provider (myIdp).

The Keycloak Terraform provider does not have the resources to configure a custom identity provider or any social provider except Google, which was the main reason to create this blog post.

Terraform is an infrastructure-as-code tool that enables the provisioning of cloud resources using code instead of manual intervention.

Using the null resource. We trigger the creation and deletion of myIdp using a local-exec provisioner.

First, let's take a look at the Terraform code block:

resource "null_resource" "myidp_identity_provider" {

  triggers = {
    myidp_idp_signature      = filesha256("${path.module}/myidp_idp.json")
    environment            = var.environment
    realm_id               = var.realm_id
    kc_admin_client_id     = var.kc_admin_client_id
    kc_admin_client_secret = var.kc_admin_client_secret
    path_module            = path.module
  }

  lifecycle {
    ignore_changes = [triggers["environment"], triggers["realm_id"], triggers["kc_admin_client_id"], triggers["kc_admin_client_secret"], triggers["path_module"]]
  }

  provisioner "local-exec" {
    command = "${path.module}/scripts/create_myidp_idp.sh ${var.environment} ${var.realm_id} ${var.kc_admin_client_id} ${var.kc_admin_client_secret} ${var.myidp_idp_client_id} ${var.myidp_idp_client_secret} ${path.module}"
  }

  provisioner "local-exec" {
    when    = destroy
    command = "${path.module}/scripts/delete_myidp_idp.sh ${self.triggers.environment} ${self.triggers.realm_id} ${self.triggers.kc_admin_client_id} ${self.triggers.kc_admin_client_secret} ${self.triggers.path_module}"
  }
}
Enter fullscreen mode Exit fullscreen mode

Resource Explanation

null resource is a resource that doesn't create any actual infrastructure but can be used to trigger provisioners. The resource assigned the name myidp_identity_provider.

Trigger Block

Next, we have a triggers block, which specifies that the resource should be triggered when certain conditions are met. In this case, the triggers are the SHA-256 hash of the myidp_idp.json file, which contains the configuration for the myidp identity provider, along with other variables such as environment, realm_id, kc_admin_client_id, and kc_admin_client_secret. The "path.module" variable is also included to ensure that the correct path is used as it will be used later to call the creation/deletion scripts.

Lifecycle Block

After the trigger block, we have a "lifecycle" block that specifies which triggers to ignore when the resource is destroyed. This is important because Terraform can't reference attributes of a resource that's being destroyed. Other than the myidp_idp_signature trigger, ignore. The additional triggers were added to be used later in the Destroy-time provisioners as their connection configurations may only reference attributes of therelated resource, via 'self', 'count.index', or 'each.key'.

Provisioner Blocks

Finally, we have two "provisioner" blocks that use the local-exec provisioner to execute shell scripts.

  • The first provisioner block creates the myidp identity provider using the variables specified in the trigger block.

  • The second provisioner block is only executed when the resource is being destroyed and deletes the myidp identity provider using the triggers stored in the resource state.

myidp_idp_signature

In the Terraform configuration file, myidp_idp_signature is a trigger that is set to the SHA-256 hash of the file located at ${path.module}/myidp_idp.json. The purpose of this trigger is to signal to Terraform that the null_resource should be recreated if the myidp_idp.json file changes.

When Terraform detects a change in the SHA-256 hash of the myidp_idp.json file, it will destroy and recreate the null_resource. This is important because the null_resource is used to execute the create_myidp_idp.sh script, which generates an Identity Provider (IDP) in Keycloak based on the contents of the myidp_idp.json file.

By using the myidp_idp_signature trigger, we ensure that the null_resource is always recreated when the myidp_idp.json file changes, which guarantees that the IDP in Keycloak is always up-to-date with the latest version of the myidp_idp.json file.

Scripts

the scripts used in the provisioner blocks handle the authentication and creation/deletion of the myidp identity provider by using the Keycloak admin API. By separating the functionality into separate scripts, it makes the code more modular and easier to maintain.

There are three scripts used in the provisioner blocks: get_kc_admin_token.sh, create_myidp_idp.sh and delete_myidp_idp.sh. let's take a look at each:

Get Keycloak Admin API Token

#!/usr/bin/env bash

environment=$1
kc_admin_client_id=$2
kc_admin_client_secret=$3
kc_url='https://keycloak-host.com'
kc_token_url="${kc_url}"'/realms/master/protocol/openid-connect/token'

# Get Access Token
response=$(curl -s -w "\n%{http_code}" "${kc_token_url}" \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'client_id='"${kc_admin_client_id}"'' \
  --data-urlencode 'client_secret='"${kc_admin_client_secret}"'' \
  --data-urlencode 'grant_type=client_credentials')

response_code=$(echo "$response" | tail -n1)
response_body=$(echo "$response" | sed '$d')

#  Check the response code
if [[ $response_code -eq 200 ]]; then
  #  Extract access token
  access_token=$(echo "$response" | jq -r '.access_token')
  #  Return the access_token
  echo "$access_token"
  exit 0
else
  echo "Error: The server returned a response code of $response_code and response body: $response_body"
  exit 1
fi

Enter fullscreen mode Exit fullscreen mode

The get_kc_admin_token.sh script is used to authenticate with the Keycloak admin API to obtain an access token. This script is called by both "create_myidp_idp.sh" and delete_myidp_idp.sh to ensure that the user has the necessary permissions to create or delete the myIdp identity provider. The script takes in the Keycloak admin URL, realm, client ID, and client secret as arguments and returns the access token as output.

Create myIdp Identity Provider

#!/usr/bin/env bash

environment=$1
realm_id=$2
kc_admin_client_id=$3
kc_admin_client_secret=$4
myidp_idp_client_id=$5
myidp_idp_client_secret=$6
path_module=$7
kc_url='https://keycloak-host.com'
kc_create_idp_url="${kc_url}"'/admin/realms/'"${realm_id}"'/identity-provider/instances'

# Get Access Token
access_token=$(source "${path_module}"/scripts/get_kc_admin_token.sh "${environment}" "${kc_admin_client_id}" "${kc_admin_client_secret}")

# Evaluate the myidp_idp.json file
myidp_idp_data=$(cat "${path_module}"/myidp_idp.json | sed "s/\${myidp_idp_client_id}/${myidp_idp_client_id}/g" | sed "s/\${myidp_idp_client_secret}/${myidp_idp_client_secret}/g")

# Make the create request and store the response code and body in variables
response=$(curl -s -w "\n%{http_code}" "$kc_create_idp_url" \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer '"$access_token"'' \
  --data "$myidp_idp_data")

response_code=$(echo "$response" | tail -n1)
response_body=$(echo "$response" | sed '$d')

# Check the response code
if [[ $response_code -eq 201 ]]; then
  echo "Success: myidp Idp created"
  exit 0
else
  echo "Error: The server returned a response code of $response_code and response body: $response_body"
  exit 1
fi

Enter fullscreen mode Exit fullscreen mode

The create_myidp_idp.sh script is used to create the myIdp identity provider. It takes in the environment, realm ID, Keycloak admin client ID and secret, myIdp identity provider client ID and secret, and the path to the module as arguments. The script first calls get_kc_admin_token.sh to authenticate with the Keycloak admin API and obtain an access token. It then uses the access token to make an HTTP POST request to the Keycloak admin API to create the myidp identity provider.

Delete myIdp Identity Provider

#!/usr/bin/env bash

environment=$1
realm_id=$2
kc_admin_client_id=$3
kc_admin_client_secret=$4
path_module=$5
kc_url='https://keycloak-host.com'
kc_delete_idp_url="${kc_url}"'/admin/realms/'"${realm_id}"'/identity-provider/instances/myidp'

# Get Access Token
access_token=$(source "${path_module}"/scripts/get_kc_admin_token.sh "${environment}" "${kc_admin_client_id}" "${kc_admin_client_secret}")

# Make the create request and store the response code and body in variables
response=$(curl -s --request DELETE -w "\n%{http_code}" "${kc_delete_idp_url}" \
  --header 'Authorization: Bearer '"$access_token"'')

response_code=$(echo "$response" | tail -n1)
response_body=$(echo "$response" | sed '$d')

# Check the response code
if [[ $response_code -eq 204 ]]; then
  echo "Success: myidp Idp deleted"
  exit 0
elif [[ $response_code -eq 404 ]]; then
  echo "Success: myidp Idp is not exist"
  exit 0
else
  echo "Error: The server returned a response code of $response_code and response body: $response_body"
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

The delete_myidp_idp.sh script is used to delete the myidp identity provider. It takes in the environment, realm ID, Keycloak admin client ID and secret, and the path to the module as arguments. The script again calls get_kc_admin_token.sh to authenticate with the Keycloak admin API and obtain an access token. It then uses the access token to make an HTTP DELETE request to the Keycloak admin API to delete the myIdp identity provider.

Conclusion

In conclusion, this Terraform code block demonstrates how to use a null resource with triggers and local-exec provisioners to create and delete a custom identity provider. By using Terraform, we can ensure that our infrastructure is consistent, repeatable, and auditable, making it easier to manage and maintain over time.

Top comments (0)