Using regular expressions in Azure Application Gateway Rewrite Rules - extract Certificate Common Name

Microsoft Azure's Application Gateway is a platform service that can offload capabilities, so you don't have to code them for yourself.
With proxy software, one capability is the ability to rewrite aspects of the HTTP request – Server, Request, and Response variables.
Another feature is Application Gateway is support for certificate authentication or mTLS (mutual TLS). In this model, Application Gateway becomes the authentication boundary, again offloading a capability that you do not have to code for. But to identify aspects of the client it's necessary to set up rewrite rules to supplement the forwarded request to your application over HTTP.
The set of mTLS headers that are available from Application Gateway are listed here: Rewrite HTTP headers and URL with Azure Application Gateway | Microsoft Learn
Example Certificate Header
Enabling the forwarding of the certificate information is as easy as setting up a simple rule.
Via the portal

Via Terraform
Here's a full example of how in Terraform to setup mTLS rewrite rules using the Azure RM Application Gateway Terraform Provider:
resource "azurerm_application_gateway" "app_gateway"
... #
rewrite_rule_set {
name = local.rewrite_rule_set_name
rewrite_rule {
name = "client_certificate_mtls"
rule_sequence = 100
request_header_configuration {
header_name = "x-client-certificate"
header_value = "{var_client_certificate}"
}
request_header_configuration {
header_name = "x-client-certificate-end-date"
header_value = "{var_client_certificate_end_date}"
}
request_header_configuration {
header_name = "x-client-certificate-fingerprint"
header_value = "{var_client_certificate_fingerprint}"
}
request_header_configuration {
header_name = "x-client-certificate-issuer"
header_value = "{var_client_certificate_issuer}"
}
request_header_configuration {
header_name = "x-client-certificate-serial"
header_value = "{var_client_certificate_serial}"
}
request_header_configuration {
header_name = "x-client-certificate-start-date"
header_value = "{var_client_certificate_start_date}"
}
request_header_configuration {
header_name = "x-client-certificate-subject"
header_value = "{var_client_certificate_subject}"
}
request_header_configuration {
header_name = "x-client-certificate-verification"
header_value = "{var_client_certificate_verification}"
}
}
}
>NOTE: sample terraform is location here.
After deploying, an mTLS request looks like the following – which uses a sample Go based diagnostic server located here.
We can see the amended headers below – and notice the subject name of the certificate as shown is CN=FOO_987654321087654321,OU=IoT,O=Contoso,L=Take,ST=NY,C=US
.
Request Headers
User-Agent: curl/7.87.0
X-Forwarded-For: 22.21.20.111:55272
X-Client-Certificate: -----BEGIN%20CERTIFICATE-----%0AMIICWTCCAgCgAwIBAgIBAjAKBggqhkjOPQQDAjA0MTIwMAYDVQQDDClBenVyZSBJ%0Ab1QgSHViIEludGVybWVkaWF0ZSBDZXJ0IFRlc3QgT25seTAeFw0yMzA0MzAxNTMy%0AMDBaFi0wXBxuc04%3D%0A-----END%20CERTIFICATE-----%0A
X-Client-Certificate-Issuer: CN=Azure IoT Hub Intermediate Cert Test Only
X-Client-Certificate-Start-Date: Apr 30 15:32:00 2023 GMT
X-Client-Cn: FOO_987654321087654321
X-Original-Host: scicoriaag1.eastus.cloudapp.azure.com
Accept: */*
X-Forwarded-Proto: https
X-Client-Certificate-Serial: 02
X-Client-Certificate-Subject: CN=FOO_987654321087654321,OU=IoT,O=Contoso,L=Take,ST=NY,C=US
Connection: keep-alive
X-Forwarded-Port: 443
X-Original-Url: /
X-Client-Certificate-End-Date: May 30 15:32:00 2023 GMT
X-Client-Certificate-Verification: SUCCESS
X-Appgw-Trace-Id: ca99b77c0afee96d6a12bff457f7be09
X-Client-Certificate-Fingerprint: 2c46ace8e01e3284e5fdd30787cc4303952bfb09
Request Details
Method: GET
Host: scicoriaag1.eastus.cloudapp.azure.com
URL: /
URI: /
Proto: HTTP/1.1
Remote: 10.21.0.6:26248
Username:
Length: 0
Env Details
HOSTNAME: myLinuxVM3
Using Regular Expressions
Now, say the back-end application just needs the common name (CN=) from the certificate subject?
For example, from above, the full subject is CN=FOO_987654321087654321,OU=IoT,O=Contoso,L=Take,ST=NY,C=US
– but our back-end application just needs the value of the CN=
part of the certificate.
For that, Application Gateway supports the use of regular expressions along with providing the matched groups to the action part of the rules.
Using the Portal
For this, we need to provide a condition, which triggers the regular expression and feeds the match groups to the action.

Using Terraform
In Terraform, we supply an additional rewrite_rule
to the rewrite_rule_set
block in the resource azurerm_application_gateway
definition as shown below:

Here is the Terraform code – note the regular expression is "CN=(.*?)[,|$]"
for the condition, and for the match group the header value is set to the pattern {var_serverVariable_#}
– so for this case it becomes {var_client_certificate_subject_1}
resource "azurerm_application_gateway" "app_gateway" {
location = azurerm_resource_group.this_resource_group.location
name = local.app_gateway_name
resource_group_name = azurerm_resource_group.this_resource_group.name
backend_address_pool {
name = local.backend_address_pool_name
}
backend_http_settings {
affinity_cookie_name = "ApplicationGatewayAffinity"
cookie_based_affinity = "Disabled"
name = local.backend_http_settings_name
port = 8080
protocol = "Http"
request_timeout = 60
}
frontend_ip_configuration {
name = local.frontend_ip_configuration_name
public_ip_address_id = azurerm_public_ip.app_gateway_public_ip.id
}
frontend_port {
name = "port_80"
port = 80
}
frontend_port {
name = local.frontend_port_name
port = 443
}
gateway_ip_configuration {
name = local.gateway_ip_configuration_name
subnet_id = azurerm_subnet.subnet_app_gateway_2.id
}
http_listener {
frontend_ip_configuration_name = local.frontend_ip_configuration_name
frontend_port_name = local.frontend_port_name
name = local.http_listener_name
protocol = "Https"
ssl_certificate_name = local.frontend_ssl_cert_name
ssl_profile_name = local.ssl_profile_name
}
identity {
identity_ids = [azurerm_user_assigned_identity.user_identity.id]
type = "UserAssigned"
}
request_routing_rule {
backend_address_pool_name = local.backend_address_pool_name
backend_http_settings_name = local.backend_http_settings_name
http_listener_name = local.http_listener_name
name = local.request_routing_rule_name
priority = 8
rewrite_rule_set_name = local.rewrite_rule_set_name
rule_type = "Basic"
}
// potential list of request headers to pass to backend pool
// https://learn.microsoft.com/en-us/azure/application-gateway/rewrite-http-headers-url#server-variables
// mTSL https://learn.microsoft.com/en-us/azure/application-gateway/rewrite-http-headers-url#mutual-authentication-server-variables
rewrite_rule_set {
name = local.rewrite_rule_set_name
rewrite_rule {
name = "client_certificate"
rule_sequence = 102
condition {
ignore_case = true
negate = false
variable = "var_client_certificate_subject"
pattern = "CN=(.*?)[,|$]"
}
request_header_configuration {
header_name = "x-client-cn"
header_value = "{var_client_certificate_subject_1}"
}
}
rewrite_rule {
name = "client_certificate_mtls"
rule_sequence = 100
request_header_configuration {
header_name = "x-client-certificate"
header_value = "{var_client_certificate}"
}
request_header_configuration {
header_name = "x-client-certificate-end-date"
header_value = "{var_client_certificate_end_date}"
}
request_header_configuration {
header_name = "x-client-certificate-fingerprint"
header_value = "{var_client_certificate_fingerprint}"
}
request_header_configuration {
header_name = "x-client-certificate-issuer"
header_value = "{var_client_certificate_issuer}"
}
request_header_configuration {
header_name = "x-client-certificate-serial"
header_value = "{var_client_certificate_serial}"
}
request_header_configuration {
header_name = "x-client-certificate-start-date"
header_value = "{var_client_certificate_start_date}"
}
request_header_configuration {
header_name = "x-client-certificate-subject"
header_value = "{var_client_certificate_subject}"
}
request_header_configuration {
header_name = "x-client-certificate-verification"
header_value = "{var_client_certificate_verification}"
}
}
}
sku {
capacity = 2
name = "WAF_v2"
tier = "WAF_v2"
}
waf_configuration {
enabled = false
firewall_mode = "Detection"
rule_set_type = "OWASP"
rule_set_version = "3.1"
# file_upload_limit_mb = 100
# max_request_body_size_kb = 128
# request_body_check = true
}