Creating Azure Apps that utilize Azure Application Proxy via Terrafrom & Graph API
Community Request: The community request can be found at the following link: GitHub Community Request.
Terraform Azure Provider Limitation: Terraform has an Azure provider that enables configuration of Azure resources, but it has a limitation when it comes to creating and managing applications that utilize application proxy. @timja provides a detailed explanation of this limitation and offers a workaround using a custom application. You can find more information about this workaround in Timja's blog post: How to Automate Azure AD Application Proxy Part 2.
I've found a workaround utilizing Terrafrom until the provider officially supports Enterprise applications & Proxy Resources. Let's get started.
The Terrafrom provider provides an example we can use to get started with. see below.
Create application from a gallery template
data "azuread_application_template" "example" {
display_name = "Marketo"
}
resource "azuread_application" "example" {
display_name = "example"
template_id = data.azuread_application_template.example.template_id
}
resource "azuread_service_principal" "example" {
application_id = azuread_application.example.application_id
use_existing = true
}
For the purpose of this documentation, we will build upon this example. The above code creates an Azure Application using a template from the Azure Gallery. To create an enterprise application that utilizes Azure Application Proxy, we need to use a specific Azure template as mentioned in Microsoft's documentation.
Step 1: Create a custom application
To configure Application Proxy for an app using the API, you first create a custom application, and then update the application's onPremisesPublishing property to configure the App Proxy settings. In this tutorial, you use an application template to create an instance of a custom application and service principal in your tenant for management. The template ID for a custom application is8adf8e6e-67b2-4cf2-a259-e3dc5476c621.
Let's modify our Terraform block to use the specific template ID:
data "azuread_application_template" "example" {
template_id = "8adf8e6e-67b2-4cf2-a259-e3dc5476c621"
}
Now that we have updated the block to use the correct template, we can proceed with defining the application:
resource "azuread_application" "example" {
display_name = "example"
template_id = data.azuread_application_template.example.template_id
feature_tags {
custom_single_sign_on = true
}
}
Next, let's create the service principal required for the enterprise application:
resource "azuread_service_principal" "example" {
application_id = azuread_application.example.application_id
owners = azuread_application.example.owners
use_existing = true
preferred_single_sign_on_mode = "saml"
login_url = "https://test.com"
feature_tags {
enterprise = true
gallery = false
custom_single_sign_on = true
}
}
At this point, if you run terraform apply, Terraform will provision a new Azure application. However, how do we configure the Azure Application to utilize Azure Application Proxy via Terraform?
One way to accomplish this is by adding a Terraform local-exec provisioner block to our resource code to call the Graph API and update the newly created application.
Let's add a block to our Terraform code that calls the Graph API and updates the application with hard-coded URLs (you can add dynamic values using a vars.tf file, for example):
resource "azuread_service_principal" "example" {
for_each = azuread_application.example
application_id = each.value.application_id
owners = each.value.owners
use_existing = true
preferred_single_sign_on_mode = "saml"
login_url = "https://test-sb.com"
feature_tags {
enterprise = true
gallery = false
custom_single_sign_on = true
}
provisioner "local-exec" {
command = <<EOF
curl --location --request PATCH 'https://graph.microsoft.com/beta/applications/${each.value.object_id}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer var.token' \
--data '{
"onPremisesPublishing": {
"externalAuthenticationType": "aadPreAuthentication",
"internalUrl": "https://internalURL.com",
"externalUrl": "https://externalURL.com",
"isHttpOnlyCookieEnabled": true,
"isOnPremPublishingEnabled": true,
"isPersistentCookieEnabled": true,
"isSecureCookieEnabled": true,
"isStateSessionEnabled": true,
"isTranslateHostHeaderEnabled": true,
"isTranslateLinksInBodyEnabled": true
}
}'
EOF
}
depends_on = [
azuread_application.example
]
}
The above Terraform block will make a call to the Graph API and update the application to utilize Azure Application Proxy URLs. However, please note that the API is only called when you create the resource.
To better manage internal and external URLs, as well as proxy connector information for Azure Application Proxy, you can utilize a variables.tf file. This file allows you to define variables that store the URLs and connector information.
For example, you can define variables such as internalurl and externalurl to store the internal and external URLs, respectively. Additionally, you can define a variable called proxy_connector_info to hold the information related to the proxy connector.
By using the variables.tf file, you can easily update the values in one place, and the changes will be reflected across all the relevant sections in your code. This approach simplifies maintenance and enhances code readability.
You can test this for your environment by using the template I created
git clone https://github.com/ctejeda/azuread_appproxy
Update the vars.tf with the information that pertains to your infrastructure. In my example, I am utilizing AWS secrets manager to store my clientid, secrectid, tenant ID, and a Azure refresh token (this gets updated on every call via the getazuretoken.sh script)
Lets issue the terraform apply -out example command

Let's apply our config. run terraform apply example
Once done, you should see a new application in your Azure tenant which utilizes Azure application proxy.

There may be alternative methods to achieve the same outcome described above, and I acknowledge that there could be better approaches. However, considering the time and simplicity required, the solution presented served my immediate needs. I welcome your thoughts and suggestions for improvement.
Hope this helps someone !