{"id":14860,"date":"2023-07-12T00:00:00","date_gmt":"2023-07-12T07:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cse\/?p=14860"},"modified":"2024-08-30T02:54:51","modified_gmt":"2024-08-30T09:54:51","slug":"temporal-mtls-sso","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/temporal-mtls-sso\/","title":{"rendered":"Temporal Mutual Transport Layer Security(mTLS) and single-sign-on(SSO) using Azure"},"content":{"rendered":"<h2>Context<\/h2>\n<p>During our engagement with a big Brazilian financial company, we did a trade study of different durable workflow orchestration platforms for microservice architecture [Azure Durable Functions, Dapr, Temporal]. Temporal was selected because of workflow readiness, Java support, and being cloud-agnostic. After that, we started to check Temporal characteristics like performance, scalability, and security. This blog focuses on sharing our findings on how Temporal secures data in transit using Mutual Transport Layer Security (mTLS) and setting up user authentication and authorization.<\/p>\n<h2>Introduction<\/h2>\n<p>The Temporal platform is designed with security in mind, and there are many features that you can use to keep both the Platform itself and your users&#8217; data secure.<\/p>\n<p>A secured Temporal Server has its network communication encrypted and has authentication and authorization protocols set up for API calls made to it. Without these, your server could be accessed by unwanted entities.<\/p>\n<p><strong>Temporal security features:<\/strong><\/p>\n<ul>\n<li><strong>Network communication:<\/strong> Mutual Transport Layer Security (mTLS) used for securing network communication with and within a Temporal cluster.<\/li>\n<li><strong>Data isolation and encryption:<\/strong>\n<ul>\n<li><strong>Namespaces:<\/strong> help isolate code (data and workflows) from each other.<\/li>\n<li><strong>Data Converter:<\/strong> helps encrypt data at rest (in data store).<\/li>\n<\/ul>\n<\/li>\n<li><strong>Authentication and Authorization:<\/strong> authenticate and control access to all API calls.\n<ul>\n<li><strong>Servers:<\/strong> To prevent spoofing and <a href=\"https:\/\/en.wikipedia.org\/wiki\/Man-in-the-middle_attack\">man in the middle attacks<\/a> you can specify the server name in the client section of your respective mTLS configuration.<\/li>\n<li><strong>Client connections:<\/strong> To restrict a client&#8217;s network access to cluster endpoints you can limit it to clients with certificates issued by a specific Certificate Authority (CA).<\/li>\n<li><strong>Users and Clients:<\/strong> To restrict access to specific users or client through extensibility plugins (<a href=\"https:\/\/docs.temporal.io\/security?lang=java#authorizer-plugin\">Authorizer<\/a> and <a href=\"https:\/\/docs.temporal.io\/security?lang=java#claim-mapper\">ClaimMapper<\/a>).<\/li>\n<li><strong>Authorizer:<\/strong> allows a wide range of authorization logic, including call target, role\/permissions claims, and other data available to the system.<\/li>\n<li><strong>ClaimMapper:<\/strong> component that extracts claims from JSON Web Tokens (JWT).<\/li>\n<li><strong>Single sign-on:<\/strong> Temporal Web offers SSO authentication by integrating with <a href=\"https:\/\/github.com\/temporalio\/web#configuring-authentication-optional\">multiple OAuth providers<\/a>.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><strong>This post consists of two parts describing how to implement critical Temporal security features using Azure services:<\/strong><\/p>\n<ul>\n<li><strong>Part 1:<\/strong> <a href=\"#part-1-configure-temporal-mtls-using-azure-keyvault-as-a-certificates-store\">Enable and configure mTLS using Azure Key Vault for generating and storing TLS certificates<\/a>.<\/li>\n<li><strong>Part 2:<\/strong> <a href=\"#part-2-temporal-authentication-and-authorization-using-azure-ad\">Users authentication and authorization using Azure Active Directory (AAD) as identity provider<\/a>.<\/li>\n<\/ul>\n<h2>Part 1: Configure Temporal mTLS using Azure KeyVault as a certificates store<\/h2>\n<p>This part demonstrates the fundamental concepts and pieces needed to setup and configure a Temporal cluster with mTLS enabled and use Azure Key Vault to generate and store the mTLS certificates in a secure way.<\/p>\n<p><em>Note: The sample is using <code>self-signed certificates signed with a custom CA<\/code> for TLS setup. For production or public-facing environments, certificates issued by a trusted CA(Certificate Authority) must be used.<\/em><\/p>\n<p><em>Complete source code here at <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\">temporalio-mtls repository<\/a>.<\/em><\/p>\n<p>Temporal supports mTLS as a way of encrypting network traffic between the <a href=\"https:\/\/docs.temporal.io\/clusters\">services of a cluster<\/a> and also between application processes and a cluster. Self-signed or properly minted certificates can be used for mTLS. mTLS is set in <a href=\"https:\/\/docs.temporal.io\/references\/configuration\/#tls\">Temporal\u2019s TLS configuration<\/a>. The configuration includes two sections where intra-cluster and external traffic can be encrypted with different sets of certificates and settings:<\/p>\n<ul>\n<li><strong>Internode:<\/strong> Configuration for encrypting communication between nodes in the cluster.<\/li>\n<li><strong>Frontend:<\/strong> Configuration for encrypting the frontend\u2019s public endpoints.<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/docs.temporal.io\/diagrams\/temporal-frontend-service.svg\" alt=\"services of a Temporal cluster\" \/><\/p>\n<h3>Self-signed vs. Trusted CA-signed Certificates<\/h3>\n<p>Let&#8217;s start with a brief overview of the certificate types that could be used in mTLS.<\/p>\n<p><strong>Self-signed certificates<\/strong> are created, issued, and signed by the same entities for whom the certificates are meant to verify their identities. This means that the individual developers or the companies that have created and\/or own the website or software in question are, essentially, signing off on themselves.<\/p>\n<p><strong>CA-signed certificate<\/strong>, on the other hand, is signed by a third-party, publicly trusted certificate authority (CA). The popular CAs are Symantec, DigiCert, GeoTrust, GlobalSign, GoDaddy, and Entrust. These entities are responsible for validating the person or organization that requests each certificate. <a href=\"https:\/\/sectigostore.com\/page\/self-signed-certificate-vs-ca\/\">read more here<\/a>.<\/p>\n<p><strong>How to use each certificate:<\/strong><\/p>\n<ul>\n<li><strong>Self-signed certificates<\/strong> are suitable for sites or services that are used in internal (intranet) or testing environments.<\/li>\n<li><strong>CA certificates<\/strong> are suitable for all public-facing websites and software.<\/li>\n<\/ul>\n<p><strong>CA (Certificate Authority) has two types:<\/strong><\/p>\n<ul>\n<li><strong>Trusted CA:<\/strong> publicly trusted certificate authority (CA) to sign certificates for production environments;<a href=\"https:\/\/en.wikipedia.org\/wiki\/Certificate_authority\">more details<\/a>.\n<ul>\n<li>Azure Key Vault natively supports (DigiCert &#8211; GlobalSign)<\/li>\n<\/ul>\n<\/li>\n<li><strong>Custom CA:<\/strong> It is created locally by the company or developer to sign and verify the self-signed certificates for internal, development, and testing environments.<\/li>\n<\/ul>\n<p><strong>Certificate generation tools:<\/strong> Azure Key Vault and OpenSSL are used for this sample.<\/p>\n<p>The following diagram shows the flow of creating a new certificate using an Azure Key Vault custom or non-integrated CA provider, <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/Key-vault\/certificates\/create-certificate\">more details<\/a><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/learn.microsoft.com\/en-us\/azure\/Key-Vault\/media\/certificate-authority-1.png\" alt=\"Create certificate using custom or non-integrated CA Provider\" \/><\/p>\n<h3>Start Temporal Cluster with mTLS\u00a0enabled<\/h3>\n<p>To start a Temporal cluster with mTLS enabled, three steps need to be executed in order:<\/p>\n<ul>\n<li><a href=\"#1-generate-the-required-certificates\">1. Generate the required certificates<\/a><\/li>\n<li><a href=\"#2-deploy-and-start-temporal-cluster\">2. Deploy and start a Temporal Cluster<\/a><\/li>\n<li><a href=\"#3-start-temporal-worker-client\">3. Start Temporal Worker<\/a><\/li>\n<\/ul>\n<h3>1. Generate the required certificates<\/h3>\n<p>Temporal has some requirements from the CA and end-entity certificates listed <a href=\"https:\/\/docs.temporal.io\/cloud\/how-to-manage-certificates-in-temporal-cloud#certificate-requirements\">here<\/a>. The simplest Temporal TLS environment requires three certificates:<\/p>\n<ul>\n<li><strong>CA certificate:<\/strong> the certificate used for signing and verifying the cluster and client certificates.<\/li>\n<li><strong>Cluster certificate:<\/strong> encrypting communication between nodes in the cluster.<\/li>\n<li><strong>Client certificate:<\/strong> encrypting communication between worker and the cluster front-end service.<\/li>\n<\/ul>\n<p>More advanced environment setup can be found in <a href=\"https:\/\/github.com\/temporalio\/samples-server\/tree\/main\/tls\/tls-full\">Temporal samples repository<\/a><\/p>\n<p>The list of configuration required to create the new certs:<\/p>\n<ul>\n<li><strong>CA:<\/strong> <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/deployment\/certs\/ca.conf\">config<\/a> and <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/deployment\/certs\/ca-cert-policy.json\">policy<\/a> files<\/li>\n<li><strong>Cluster:<\/strong> <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/deployment\/certs\/cluster.conf\">config<\/a> and <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/deployment\/certs\/cluster-cert-policy.json\">policy<\/a> files<\/li>\n<li><strong>Client:<\/strong> <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/deployment\/certs\/client.conf\">config<\/a> and <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/deployment\/certs\/client-cert-policy.json\">policy<\/a> files<\/li>\n<\/ul>\n<p>Full source code <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/deployment\/certs\/generate-test-certs-keyvault.sh\">generate-test-certs-keyvault.sh<\/a><\/p>\n<pre><code class=\"language-bash\">###################################################\r\n# Create a custom CA certificate to be used for \r\n# signing both cluster and client the certificate\r\n###################################################\r\n# Variables\r\ncertDir=.\/certs\r\ncertName=\"ca\"\r\nkeyVault=\"&lt;key-vault-name&gt;\"\r\n\r\n# `az rest` is used instead of `az keyvault certificate create` \r\n# because the latter does not support creating a CA self-signed certificate\r\n# https:\/\/github.com\/Azure\/azure-cli\/issues\/18178\r\naz rest \\\r\n    --method post \\\r\n    --body @$certName-cert-policy.json \\\r\n    --resource \"https:\/\/vault.azure.net\" \\\r\n    --headers '{\"content-type\":\"application\/json\"}' \\\r\n    --uri \"https:\/\/$keyVault.vault.azure.net\/certificates\/$certName\/create\" \\\r\n    --uri-parameters 'api-version=7.2'\r\n\r\n# Wait for cert to be ready\r\nstatus=\"inProgress\"\r\nwhile [ \"$status\" != \"completed\" ]\r\ndo\r\n    status=$(az keyvault certificate pending show --vault-name=$keyVault --name=$certName --query status --output tsv)\r\n    sleep 1\r\ndone\r\n\r\n# Download the certificate from Key Vault\r\naz keyvault secret download --vault-name $keyVault --name $certName --file \"$certDir\/$certName.pem\"\r\n\r\n# Export private key\r\nopenssl pkey -in \"$certDir\/$certName.pem\" -out \"$certDir\/$certName.key\"\r\n\r\n# Export certificate\r\nopenssl x509 -in \"$certDir\/$certName.pem\" -out \"$certDir\/$certName.cert\"<\/code><\/pre>\n<pre><code class=\"language-bash\">###################################################\r\n# Create a certificate signed by the custom CA certificate generated above\r\n# The same script is used to generate both the cluster and client certs\r\n###################################################\r\n\r\n# Variables\r\ncertDir=.\/certs\r\ncertName=\"cluster\"\r\ncaCertName=\"ca\"\r\nkeyVault=\"&lt;key-vault-name&gt;\"\r\n\r\n# Create a new certificate in Key Vault\r\nCSR=$(az keyvault certificate create --vault-name $keyVault --name $certName --policy \"@$certName-cert-policy.json\" | jq -r '.csr')\r\n\r\n# Create the certificate CSR(Certificate signing request) file\r\nCSR_FILE_CONTENT=\"-----BEGIN CERTIFICATE REQUEST-----\\n$CSR\\n-----END CERTIFICATE REQUEST-----\"\r\necho -e $CSR_FILE_CONTENT &gt;&gt; \"$certDir\/$certName.csr\"\r\n\r\n# Download the private key from Key Vault in PEM Format\r\naz keyvault secret download --vault-name $keyVault --name $certName --file \"$certDir\/$certName.pem\"\r\n\r\n# Export private key from the downloaded pem file\r\nopenssl pkey -in \"$certDir\/$certName.pem\" -out \"$certDir\/$certName.key\"\r\n\r\n# Create the signed certificate using the downloaded CSR (certificate signing request)\r\nopenssl x509 -req -in $certDir\/$certName.csr -CA $caCertName.cert -CAkey $caCertName.key -sha256 -CAcreateserial -out $certDir\/$certName.cert -days 365 -extfile $certName.conf -extensions $exts\r\nlocal chain_file=\"$certDir\/$certName.cert\"\r\nif [[ $no_chain_opt != no_chain ]]; then\r\n    chain_file=\"$certDir\/$certName-chain.cert\"\r\n    cat $certDir\/$certName.cert $caCertName.cert &gt; $chain_file\r\nfi\r\n\r\n# Generate the updated pem file with both cert and key to be imported into the Key Vault\r\ncat \"$certDir\/$certName.key\" $chain_file &gt; \"$certDir\/$certName.pem\"\r\n\r\n# Import new pem into the Key Vault to be safely stored and used by the application\r\nCERT_IMPORT_RESPONSE=$(az keyvault certificate import --vault-name $keyVault --name $certName --file \"$certDir\/$certName.pem\")<\/code><\/pre>\n<h3>2. Deploy and start Temporal cluster<\/h3>\n<p>After generating the certificates, we can now start up the Temporal cluster using:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/deployment\/tls-simple\/docker-compose.yml\"><strong>docker-compose.yml<\/strong><\/a>: contains the definition for cluster nodes and configurations.<\/li>\n<li><a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/deployment\/tls-simple\/start-temporal.sh\"><strong>start-temporal.sh<\/strong><\/a>: Script to set the environment variables and compose the cluster.<\/li>\n<\/ul>\n<h3>3. Start Temporal Worker (Client)<\/h3>\n<p>Full source code <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/src\/main\/java\/com\/temporal\/samples\/helloworld\/Client.java\">Client.java<\/a><\/p>\n<pre><code class=\"language-java\">\/\/ client certificate, which should look like:\r\nInputStream clientCert = new FileInputStream(env.getProperty(\"temporal.tls.client.certPath\"));\r\n\/\/ client key, which should look like:\r\nInputStream clientKey = new FileInputStream(env.getProperty(\"temporal.tls.client.keyPath\"));\r\n\/\/ certification Authority signing certificate\r\nInputStream caCert = new FileInputStream(env.getProperty(\"temporal.tls.ca.certPath\"));\r\n\r\n \/\/ Create SSL Context using the client certificate and key\r\n var sslContext = GrpcSslContexts.configure(SslContextBuilder\r\n    .forClient()\r\n    .keyManager(clientCert, clientKey)\r\n    .trustManager(caCert))\r\n    .build();\r\n\r\n\/*\r\n* Get a Workflow service temporalClient which can be used to start, Signal, and\r\n* Query Workflow Executions, This gRPC stubs wrapper talks to the Temporal service.\r\n*\/\r\nWorkflowServiceStubs service = WorkflowServiceStubs.newServiceStubs(\r\n            WorkflowServiceStubsOptions\r\n                .newBuilder()\r\n                .setSslContext(sslContext)\r\n                .setTarget(temporalServerUrl)\r\n                .setChannelInitializer(c -&gt; c.overrideAuthority(temporalServerCertAuthorityName)) \/\/ Override the server name used for TLS handshakes\r\n                .build());\r\n\r\n\/\/ WorkflowClient can be used to start, signal, query, cancel, and terminate Workflows.\r\nworkflowClient = WorkflowClient.newInstance(service);<\/code><\/pre>\n<p><a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls#get-started\">Please follow this readme to run the full sample on your local\u00a0machine<\/a><\/p>\n<h2>Part 2: Temporal Authentication and Authorization using Azure AD<\/h2>\n<p>This part describes how to enable single-sign-on(SSO) for Temporal web UI users and using the Azure Active directory (AAD) as Oauth identity provider for authenticating users and generating JWT access tokens.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/docs.temporal.io\/assets\/images\/frontend-authorization-order-of-operations-32581c182f2650f3c54796791e4cbcf1.png\" alt=\"The authorization and claim mapping logic\" \/><\/p>\n<p><strong>These steps will be used to enable Temporal SSO using Azure AD:<\/strong><\/p>\n<ul>\n<li>Create an app registration in AAD for authentication<\/li>\n<li>Create app roles to set users\u2019 permissions per namespace<\/li>\n<li>Enable SSO for Temporal Web UI configuration<\/li>\n<li>Query an access token used for Temporal Worker apps and CLI (tctl)<\/li>\n<\/ul>\n<h3>1. Creating an App Registration in AAD<\/h3>\n<p>When you register your application, Azure AD assigns a unique Application ID to it and allows you to add certain capabilities such as credentials, permissions, and sign-ons. The default settings allow only users from the tenant under which your app is registered to sign into your application.<\/p>\n<h4>Step 1 (Create app registration)<\/h4>\n<ul>\n<li>In Azure portal, select <code>Azure Active Directory<\/code> and then <code>App registrations<\/code>.<\/li>\n<li>Click on <code>New registration<\/code> and fill in the following info:\n<ul>\n<li>Name: <code>temporal-auth-demo<\/code><\/li>\n<li>Other fields: leave default<\/li>\n<\/ul>\n<\/li>\n<li>Click on <code>Register<\/code> to create application.<\/li>\n<\/ul>\n<h4>Step 2 (Create login Redirect URIs)<\/h4>\n<ul>\n<li>Go to <code>Authentication<\/code> menu, click on <code>Add a platform<\/code> and select <code>Web<\/code>.<\/li>\n<li>Later in this post, we will be running the Temporal Web UI locally at port 8080 so to enable SSO, we need to add a redirect URI for it: <code>http:\/\/localhost:8080\/auth\/sso\/callback<\/code>\n<img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-redirect-uris.png\" alt=\"setting up the login redirect URI\" \/><\/li>\n<li>Remember to check <code>Access tokens<\/code> and click on <code>Configure<\/code>.\n<img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-access-\" alt=\"Configure access tokens\" \/><\/li>\n<li>Click on <code>Add URI<\/code> to add another one for Postman, which will be used later for querying access tokens for Temporal CLI: <code>https:\/\/oauth.pstmn.io\/v1\/callback<\/code>\n<img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-postman-redirect-uri.png\" alt=\"setting up the login redirect URI for postman\" \/><\/li>\n<li>Click on <code>Save<\/code> to finish.<\/li>\n<\/ul>\n<h4>Step 3 (Create a default scope)<\/h4>\n<p>Next we will need to create a default scope for the app registration.<\/p>\n<ul>\n<li>Navigate to <code>Expose an API<\/code> menu, click on <code>Add a scope<\/code>.<\/li>\n<li>Leave the application ID URI as its default value: <code>api:\/\/{Client ID}<\/code> and click on <code>Save and continue<\/code>.<\/li>\n<li>Set the scope name to <code>default<\/code> and fill in other required fields.<\/li>\n<\/ul>\n<h4>Step 4 (Create a client secret)<\/h4>\n<ul>\n<li>Go to <code>Certificates &amp; secrets<\/code> menu<\/li>\n<li>Click on <code>New client secret<\/code><\/li>\n<li>Enter a description for the secret and click on <code>Add<\/code>.<\/li>\n<li>Remember to copy the client secret\u2019s value for later use.<\/li>\n<\/ul>\n<h4>Step 5 (Testing with Postman)<\/h4>\n<p>At this point, we can try querying an access token using Postman.<\/p>\n<p>Open Postman and go to <code>Authorization<\/code> tab, then select:\nType: <code>OAuth 2.0<\/code>\nGrant Type: <code>Authorization Code<\/code>\nCheck <code>Authorize using browser<\/code><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-postman-authorization.png\" alt=\"Testing with Postman\" \/><\/p>\n<p>Fill in the required info as below:<\/p>\n<ul>\n<li>Auth URL: <code>https:\/\/login.microsoftonline.com\/{Tenant ID}\/oauth2\/v2.0\/authorize<\/code><\/li>\n<li>Access Token URL: <code>https:\/\/login.microsoftonline.com\/{Tenant ID}\/oauth2\/v2.0\/token<\/code><\/li>\n<li>Client ID: The application (client) ID<\/li>\n<li>Client Secret: The application (client) secret<\/li>\n<li>Scope: <code>api:\/\/{Client ID}\/default<\/code><\/li>\n<li>Then click on <code>Get New Access Token<\/code> and sign in using your browser:<\/li>\n<li>Make sure that your browser allows pop-up windows and can communicate with Postman to provide access tokens. If everything works properly, you can click on <code>Proceed<\/code> in Postman to see the returned access token:<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-postman-\" alt=\"Authentication complete\" \/><\/p>\n<p>Finally, go to <a href=\"https:\/\/jwt.ms\/\">jwt.ms<\/a> to decode the access token and check all available claims. We will need to add more claims later for authorization.<\/p>\n<h3>2. Creating App Roles for User Permissions<\/h3>\n<p>Basing on <a href=\"https:\/\/docs.temporal.io\/security#example-of-a-payload-for-the-default-jwt-claimmapper\">the security docs<\/a> on the Temporal website, the access token needs to have a <code>permissions<\/code> claim containing a collection of roles for the caller:<\/p>\n<pre><code class=\"language-json\"> \"permissions\":[\r\n    \"system:read\",\r\n    \"default:write\"\r\n ]<\/code><\/pre>\n<p>In the example above, the caller have the <code>read<\/code> permission to the <code>system<\/code> namespace and the <code>write<\/code> permission to the <code>default<\/code> namespace.<\/p>\n<p>To create these claims in AAD, we can use App roles.<\/p>\n<p><strong><em>Step 1 (Create App roles)<\/em><\/strong><\/p>\n<ul>\n<li>In Azure portal, go to the <code>temporal-auth-demo<\/code> app registration.<\/li>\n<li>Navigate to <code>App roles<\/code> menu, click on <code>Create app role<\/code> and fill in the following info to create a new role:\n<ul>\n<li>Display name: <code>system:read<\/code><\/li>\n<li>Allowed member types: Users\/Groups<\/li>\n<li>Value: <code>system:read<\/code><\/li>\n<li>Description: <code>system:read<\/code><\/li>\n<li>Check <code>Do you want to enable this app role?<\/code><\/li>\n<li>Click on <code>Apply<\/code><\/li>\n<\/ul>\n<\/li>\n<li>Next click on <code>Create app role<\/code> again to add another role for <code>default:write<\/code>:<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-app-roles.png\" alt=\"App Roles list\" \/><\/p>\n<p><strong><em>Step 2 (Assign roles to a user)<\/em><\/strong><\/p>\n<p>To assign roles to a user, click on <code>How do I assign App roles<\/code> and then <code>Enterprise applications<\/code>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-app-roles-assign.png\" alt=\"Assign roles to a user\" \/><\/p>\n<p>Then click on <code>Assign users and groups<\/code>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-app-roles-users.png\" alt=\"Assign users and groups\" \/><\/p>\n<p>Click on <code>Add user\/group<\/code> to start adding assignment. Select a user and assign him\/her both roles: <code>system:read<\/code> and <code>default:write<\/code>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-app-roles-assign-view.png\" alt=\"Add Assignment\" \/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-app-roles-assign-list.png\" alt=\"Assign roles to a user\" \/><\/p>\n<p><strong><em>Step 3 (Testing with Postman)<\/em><\/strong><\/p>\n<p>Now try querying an access token using Postman again and notice the following new claims have been added:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-\" alt=\"Testing with Postman\" \/><\/p>\n<p>Now you may wonder that the claim name is <code>roles<\/code> instead of <code>permissions<\/code>, which is expected by Temporal. We will address this issue in the next section.<\/p>\n<h3>3. Running Temporal Server with authorization enabled<\/h3>\n<p>In this example, we will be using this <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\">repository<\/a> and running the Temporal server locally.<\/p>\n<p>After cloning the repository, make the following changes:<\/p>\n<p>1- In the <code>deployment\/tls-simple\/docker-compose.yml<\/code> file, enable authorization in the Temporal server:<\/p>\n<pre><code class=\"language-yaml\">temporal:\r\n    image: temporalio\/auto-setup:${SERVER_TAG:-latest}\r\n    ports:\r\n      - \"7233:7233\"\r\n    volumes:\r\n      - ${DYNAMIC_CONFIG_DIR:-..\/config\/dynamicconfig}:\/etc\/temporal\/config\/dynamicconfig\r\n      - ${TEMPORAL_LOCAL_CERT_DIR}:${TEMPORAL_TLS_CERTS_DIR}\r\n    environment:\r\n      - \"CASSANDRA_SEEDS=cassandra\"\r\n      - \"DYNAMIC_CONFIG_FILE_PATH=config\/dynamicconfig\/development.yaml\"\r\n      - \"SERVICES=frontend:matching:history:internal-frontend:worker\"\r\n      - \"SKIP_DEFAULT_NAMESPACE_CREATION=false\"\r\n      - \"TEMPORAL_TLS_SERVER_CA_CERT=${TEMPORAL_TLS_CERTS_DIR}\/ca.cert\"\r\n      - \"TEMPORAL_TLS_SERVER_CERT=${TEMPORAL_TLS_CERTS_DIR}\/cluster.cert\"\r\n      - \"TEMPORAL_TLS_SERVER_KEY=${TEMPORAL_TLS_CERTS_DIR}\/cluster.key\"\r\n      - \"TEMPORAL_TLS_REQUIRE_CLIENT_AUTH=true\"\r\n      - \"TEMPORAL_TLS_FRONTEND_CERT=${TEMPORAL_TLS_CERTS_DIR}\/cluster.cert\"\r\n      - \"TEMPORAL_TLS_FRONTEND_KEY=${TEMPORAL_TLS_CERTS_DIR}\/cluster.key\"\r\n      - \"TEMPORAL_TLS_CLIENT1_CA_CERT=${TEMPORAL_TLS_CERTS_DIR}\/ca.cert\"\r\n      - \"TEMPORAL_TLS_CLIENT2_CA_CERT=${TEMPORAL_TLS_CERTS_DIR}\/ca.cert\"\r\n      - \"TEMPORAL_TLS_INTERNODE_SERVER_NAME=tls-sample\"\r\n      - \"TEMPORAL_TLS_FRONTEND_SERVER_NAME=tls-sample\"\r\n      - \"TEMPORAL_TLS_FRONTEND_DISABLE_HOST_VERIFICATION=false\"\r\n      - \"TEMPORAL_TLS_INTERNODE_DISABLE_HOST_VERIFICATION=false\"\r\n      - \"TEMPORAL_CLI_ADDRESS=temporal:7236\"\r\n      - \"TEMPORAL_CLI_TLS_CA=${TEMPORAL_TLS_CERTS_DIR}\/ca.cert\"\r\n      - \"TEMPORAL_CLI_TLS_CERT=${TEMPORAL_TLS_CERTS_DIR}\/cluster.cert\"\r\n      - \"TEMPORAL_CLI_TLS_KEY=${TEMPORAL_TLS_CERTS_DIR}\/cluster.key\"\r\n      - \"TEMPORAL_CLI_TLS_ENABLE_HOST_VERIFICATION=true\"\r\n      - \"TEMPORAL_CLI_TLS_SERVER_NAME=tls-sample\"\r\n      # Enable jwt authorization\r\n      - \"USE_INTERNAL_FRONTEND=true\"\r\n      # Enable default authorizer and claim mapper\r\n      - \"TEMPORAL_AUTH_AUTHORIZER=default\"\r\n      - \"TEMPORAL_AUTH_CLAIM_MAPPER=default\"\r\n      # specify the permissions source property in jwt token\r\n      - \"TEMPORAL_JWT_PERMISSIONS_CLAIM=roles\"\r\n      # JWK containing the public keys used to verify access tokens\r\n      - \"TEMPORAL_JWT_KEY_SOURCE1=https:\/\/login.microsoftonline.com\/{Tenant ID}\/discovery\/v2.0\/keys\"\r\n      - \"TEMPORAL_JWT_KEY_REFRESH=30m\"<\/code><\/pre>\n<p>2- In the <code>deployment\/tls-simple\/docker-compose.yml<\/code> file, enable SSO in the Temporal Web UI:<\/p>\n<pre><code class=\"language-yaml\">temporal-ui:\r\n    image: temporalio\/ui:${UI_TAG:-latest}\r\n    ports:\r\n      - \"8080:8080\"\r\n    volumes:\r\n      - ${TEMPORAL_LOCAL_CERT_DIR}:${TEMPORAL_TLS_CERTS_DIR}\r\n    environment:\r\n      - \"TEMPORAL_ADDRESS=temporal:7233\"\r\n      - \"TEMPORAL_TLS_CA=${TEMPORAL_TLS_CERTS_DIR}\/ca.cert\"\r\n      - \"TEMPORAL_TLS_CERT=${TEMPORAL_TLS_CERTS_DIR}\/cluster.cert\"\r\n      - \"TEMPORAL_TLS_KEY=${TEMPORAL_TLS_CERTS_DIR}\/cluster.key\"\r\n      - \"TEMPORAL_TLS_ENABLE_HOST_VERIFICATION=true\"\r\n      - \"TEMPORAL_TLS_SERVER_NAME=tls-sample\"\r\n      # Uncomment to enable SSO authentication and authorization\r\n      - \"TEMPORAL_AUTH_ENABLED=true\"\r\n      # Specify authorization server and issuer \r\n      - \"TEMPORAL_AUTH_PROVIDER_URL=https:\/\/login.microsoftonline.com\/{Tenant ID}\/v2.0\"\r\n      - \"TEMPORAL_AUTH_ISSUER_URL=https:\/\/login.microsoftonline.com\/{Tenant ID}\/v2.0\"\r\n      # Specify client ID and secret\r\n      - \"TEMPORAL_AUTH_CLIENT_ID={Client ID}\"\r\n      - \"TEMPORAL_AUTH_CLIENT_SECRET={Client Secret}\"\r\n      # Specify callback URL which is the redirect URI in the app registration\r\n      - \"TEMPORAL_AUTH_CALLBACK_URL=http:\/\/localhost:8080\/auth\/sso\/callback\"\r\n      # Specify the authentication scope\r\n      - \"TEMPORAL_AUTH_SCOPES=openid,api:\/\/{Client ID}\/default\"\r\n    depends_on:\r\n      - temporal<\/code><\/pre>\n<p>3- Run <code>make start-cluster-mtls<\/code> command to start the Temporal server and other components.<\/p>\n<p>Now you can test the Temporal Web UI at <a href=\"http:\/\/localhost:8080\">http:\/\/localhost:8080<\/a>. It does require users to login with their AAD credentials:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-temporal-sso.png\" alt=\"Temporal UI sso\" \/><\/p>\n<p>After signing in successfully, users can only access the namespaces that are granted to them. In the following screenshot, you can see all workflows in the default namespace since you have the <code>default:write<\/code> value in the permission claim:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2023\/07\/aad-temporal-wf-ls.png\" alt=\"Temporal UI sso default name space\" \/><\/p>\n<p>To test Temporal access using tctl, use Postman to grab an access token and run the following command to list all namespaces:<\/p>\n<pre><code class=\"language-bash\"># Replace the access token below\r\nauth=\"Bearer {Access Token}\"\r\ntctl --auth \"$auth\" namespace list<\/code><\/pre>\n<h3>4. Start Temporal Worker (WorkflowClient)<\/h3>\n<p>Full source code <a href=\"https:\/\/github.com\/Aymalla\/temporalio-mtls\/tree\/main\/src\/main\/java\/com\/temporal\/samples\/helloworld\/Client.java\">Client.java<\/a><\/p>\n<pre><code class=\"language-java\">\/\/ Implement code to retrieve an access token, then provide it below.\r\n AuthorizationTokenSupplier tokenSupplier = () -&gt; \"Bearer {Access Token}\";\r\n\r\nWorkflowServiceStubs service = WorkflowServiceStubs.newServiceStubs(\r\n    WorkflowServiceStubsOptions\r\n        .newBuilder()\r\n        .setSslContext(sslContext)\r\n        .setTarget(temporalServerUrl)\r\n        .addGrpcMetadataProvider(new AuthorizationGrpcMetadataProvider(tokenSupplier)) \/\/ set token provider\r\n        .build());\r\n<\/code><\/pre>\n<p>Run <code>make start-worker<\/code> to start the Temporal WorkflowClient (Worker)<\/p>\n<h2>Conclusion<\/h2>\n<p>A secured Temporal server has its network communication encrypted and has authentication and authorization protocols set up for API calls made to it. Without these, your server could be accessed by unwanted entities. Temporal has many features that you can use to keep both the platform and your users&#8217; data secure, and we can leverage Azure services to implement these features to provide scalable enterprise level solution.<\/p>\n<h2>References<\/h2>\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/Key-Vault\/certificates\/create-certificate\">Azure Key Vault certificate creation methods<\/a><\/li>\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/cli\/azure\/keyvault\/certificate\">Azure certificate CLI<\/a><\/li>\n<li><a href=\"https:\/\/docs.temporal.io\/cloud\/how-to-manage-certificates-in-temporal-cloud\">How to manage certificates in Temporal<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/temporalio\/samples-java\">Temporal Java Samples<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/temporalio\/samples-server\/tree\/main\/tls\/tls-simple\">Temporal Server Samples<\/a><\/li>\n<li><a href=\"https:\/\/docs.temporal.io\/security?lang=java\">Temporal Platform security features<\/a><\/li>\n<li><a href=\"https:\/\/sectigostore.com\/page\/self-signed-certificate-vs-ca\/\">Self Signed Certificate vs CA Certificate<\/a><\/li>\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/active-directory\/develop\/quickstart-register-app\">Register an application with Azure AD<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>This guide explores Temporal platform security features and how it can be integrated with Azure Key Vault and Azure Active Directory.<\/p>\n","protected":false},"author":119131,"featured_media":14876,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[3419,80,3416,3417],"class_list":["post-14860","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cse","tag-azure-active-directoryaad","tag-azure-key-vault","tag-temporal","tag-workflow-engine"],"acf":[],"blog_post_summary":"<p>This guide explores Temporal platform security features and how it can be integrated with Azure Key Vault and Azure Active Directory.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/14860","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/119131"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=14860"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/14860\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/14876"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=14860"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=14860"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=14860"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}