diff --git a/content/aro/frontdoor/images/fd-manager.png b/content/aro/frontdoor/images/fd-manager.png
new file mode 100644
index 000000000..6994bdb80
Binary files /dev/null and b/content/aro/frontdoor/images/fd-manager.png differ
diff --git a/content/aro/frontdoor/images/minesweeper.png b/content/aro/frontdoor/images/minesweeper.png
deleted file mode 100644
index 2144c707a..000000000
Binary files a/content/aro/frontdoor/images/minesweeper.png and /dev/null differ
diff --git a/content/aro/frontdoor/index.md b/content/aro/frontdoor/index.md
index 3824604ea..969b37e4d 100644
--- a/content/aro/frontdoor/index.md
+++ b/content/aro/frontdoor/index.md
@@ -1,494 +1,995 @@
---
date: '2023-03-29'
-title: Azure Front Door with ARO ( Azure Red Hat OpenShift )
+title: Exposing Applications on a Private ARO Cluster with Azure Front Door
tags: ["ARO"]
authors:
- Kevin Collins
- Ricardo Martins
+ - Diana Sari
+validated_version: "4.20"
---
-Securing exposing an Internet facing application with a private ARO Cluster.
-When you create a cluster on ARO you have several options in making the cluster public or private. With a public cluster you are allowing Internet traffic to the api and *.apps endpoints. With a private cluster you can make either or both the api and .apps endpoints private.
+Azure Front Door Premium can expose applications running on an Azure Red Hat OpenShift (ARO) cluster while keeping the cluster ingress private. Azure Front Door receives public client traffic at the edge and reaches the ARO internal ingress load balancer through Azure Private Link.
+
+This guide configures the following traffic flow:
+
+```text
+Internet client
+ |
+ v
+Azure Front Door Premium
+ |
+ v
+Azure Front Door managed private endpoint
+ |
+ v
+Azure Private Link Service
+ |
+ v
+ARO internal ingress load balancer
+ |
+ v
+OpenShift ingress router
+ |
+ v
+Application Route
+```
+
+{{< alert >}}
+Azure Front Door must send a `Host` header that matches an OpenShift Route. When the ARO ingress load balancer IP is used as the origin host name, set the Azure Front Door **origin host header** to the application Route hostname.
+
+If the origin host header is left as the load balancer IP, the OpenShift router cannot match the request to a Route and returns `503 Application is not available`. The same requirement applies to Azure Front Door health probes.
+{{< /alert >}}
+
+## Scope and Design Considerations
+
+This guide uses one application hostname and one Azure Front Door origin configuration.
-How can you allow Internet access to an application running on your private cluster where the .apps endpoint is private? This document will guide you through using Azure Frontdoor to expose your applications to the Internet. There are several advantages of this approach, namely your cluster and all the resources in your Azure account can remain private, providing you an extra layer of security. Azure FrontDoor operates at the edge so we are controlling traffic before it even gets into your Azure account. On top of that, Azure FrontDoor also offers WAF and DDoS protection, certificate management and SSL Offloading just to name a few benefits.
+A static origin host header is appropriate when an origin group represents a specific application hostname. If you need to publish multiple OpenShift Route hostnames, use one of the following patterns:
-*Adopted from [ARO Reference Architecture](https://github.com/UmarMohamedUsman/aro-reference-architecture)*
+* Create a separate Azure Front Door origin or origin group for each application hostname, with the matching origin host header.
+* Place a host-aware reverse proxy between Azure Front Door and the ARO ingress router.
+* Disable health probes for a single-origin group only when the loss of origin-health monitoring is acceptable.
+Do not configure one shared IP origin with a static host header and expect it to transparently route arbitrary application hostnames. Azure Front Door replaces the backend `Host` header with the configured origin host header.
## Prerequisites
-* az cli
-* oc cli
-* a custom domain
-* a DNS zone that you can easily modify
-
-To build and deploy the application
-* [maven cli](https://maven.apache.org/install.html)
-* [quarkus cli](https://quarkus.io/guides/cli-tooling)
-* [OpenJDK Java 8](https://www.azul.com/downloads/?package=jdk)
+* A private ARO cluster with private ingress
+* Azure CLI with the `cdn` extension
+* OpenShift CLI (`oc`)
+* Access to the private ARO API
+* Permission to create:
+ * A subnet in the ARO virtual network
+ * An Azure Private Link Service
+ * An Azure Front Door Premium profile
+ * DNS records for the application domain
+* A DNS domain that you control
+* An unused subnet CIDR within the ARO virtual network
-Make sure to use the same terminal session while going through guide for all commands as we will reference envrionment variables set or created through the guide.
+Install or update the Azure CLI extension:
-## Get Started
+```bash
+az extension add --name cdn --upgrade
+```
- * Create a private ARO cluster.
+Log in to Azure and OpenShift before continuing:
- Follow this guide to [Create a private ARO cluster](/experts/aro/private-cluster)
- or simply run this [bash script](../private-cluster/create-cluster.sh)
+```bash
+az login
+oc whoami
+```
-## Set Evironment Variables
+Use the same terminal session throughout this guide because later commands reference previously defined environment variables.
-1. Manually set environment variables
+## Set Environment Variables
- ```
- AROCLUSTER=
+Set the ARO cluster, Azure resource group, application hostname, and Azure Front Door names:
- ARORG=
+```bash
+export ARO_CLUSTER=""
+export ARO_RESOURCE_GROUP=""
+export APP_FQDN=""
+
+export AFD_PROFILE=""
+export AFD_ENDPOINT=""
+export AFD_ORIGIN_GROUP="aro-origin-group"
+export AFD_ORIGIN="aro-ingress"
+export AFD_ROUTE="aro-route"
+
+export PLS_NAME="${ARO_CLUSTER}-frontdoor-pls"
+export PLS_SUBNET_NAME="frontdoor-pls-subnet"
+export PLS_SUBNET_PREFIX=""
+```
- AFD_NAME=
+Example:
- DOMAIN='e.g. aro.kmobb.com' This is the domain that you will be adding to Azure DNS to manage.
+```bash
+export APP_FQDN="hello.apps.example.com"
+export PLS_SUBNET_PREFIX="10.0.8.0/27"
+```
- ARO_APP_FQDN='e.g. minesweeper.aro.kmobb.com'
- (note - we will be deploying an application called minesweeper to test front door. Select a domain you would like to use for the application. For example minesweeper.aro.kmobb.com ... where aro.kmobb.com is the domain you manage and have DNS access to.)
+Retrieve cluster and network information:
- AFD_MINE_CUSTOM_DOMAIN_NAME='minesweeper-aro-kmobb-com'
- (note - this should be your domain name without and .'s for example minesweeper-aro-kmobb-com)
+```bash
+export LOCATION=$(az aro show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --name "$ARO_CLUSTER" \
+ --query location \
+ -o tsv)
+
+export ARO_INFRA_RESOURCE_GROUP=$(az aro show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --name "$ARO_CLUSTER" \
+ --query clusterProfile.resourceGroupId \
+ -o tsv | awk -F/ '{print $NF}')
+
+export WORKER_SUBNET_ID=$(az aro show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --name "$ARO_CLUSTER" \
+ --query workerProfiles[0].subnetId \
+ -o tsv)
+
+export VNET_RESOURCE_GROUP=$(echo "$WORKER_SUBNET_ID" | awk -F/ '{print $5}')
+export VNET_NAME=$(echo "$WORKER_SUBNET_ID" | awk -F/ '{print $9}')
+```
- PRIVATEENDPOINTSUBNET_PREFIX= subnet in the VNET you cluster is in. If you following the example above to create a custer where you virtual network is 10.0.0.0/20 then you can use '10.0.6.0/24'
+Confirm that both the API and ingress are private:
- PRIVATEENDPOINTSUBNET_NAME='PrivateEndpoint-subnet'
- ```
+```bash
+az aro show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --name "$ARO_CLUSTER" \
+ --query '{
+ apiVisibility:apiserverProfile.visibility,
+ ingressVisibility:ingressProfiles[0].visibility,
+ infrastructureResourceGroup:clusterProfile.resourceGroupId
+ }' \
+ -o yaml
+```
-1. Set environment variables with Bash
+Expected output:
- ```bash
- UNIQUEID=$RANDOM
+```yaml
+apiVisibility: Private
+ingressVisibility: Private
+```
- ARO_RGNAME=$(az aro show -n $AROCLUSTER -g $ARORG --query "clusterProfile.resourceGroupId" -o tsv | sed 's/.*\///')
+## Deploy a Test Application
- LOCATION=$(az aro show --name $AROCLUSTER --resource-group $ARORG --query location -o tsv)
+Create a project and deploy a small HTTP application:
- INTERNAL_LBNAME=$(az network lb list --resource-group $ARO_RGNAME --query "[? contains(name, 'internal')].name" -o tsv)
+```bash
+oc new-project afd-test
- WORKER_SUBNET_NAME=$(az aro show --name $AROCLUSTER --resource-group $ARORG --query 'workerProfiles[0].subnetId' -o tsv | sed 's/.*\///')
+oc create deployment hello \
+ --image=quay.io/openshift/origin-hello-openshift:latest
- WORKER_SUBNET_ID=$(az aro show --name $AROCLUSTER --resource-group $ARORG --query 'workerProfiles[0].subnetId' -o tsv)
+oc expose deployment hello --port=8080
- VNET_NAME=$(az network vnet list -g $ARORG --query '[0].name' -o tsv)
+oc expose service hello \
+ --hostname="$APP_FQDN"
+```
- LBCONFIG_ID=$(az network lb frontend-ip list -g $ARO_RGNAME --lb-name $INTERNAL_LBNAME --query "[? contains(subnet.id,'$WORKER_SUBNET_ID')].id" -o tsv)
+Wait for the deployment:
- LBCONFIG_IP=$(az network lb frontend-ip list -g $ARO_RGNAME --lb-name $INTERNAL_LBNAME --query "[? contains(subnet.id,'$WORKER_SUBNET_ID')].privateIPAddress" -o tsv)
+```bash
+oc rollout status deployment/hello -n afd-test
+```
- ```
-## Create a Private Link Service
-After we have the cluster up and running, we need to create a private link service. The private link service will provide private and secure connectivity between the Front Door Service and our cluster.
+Verify the Route:
-1. Disable the worker subnet private link service network policy for the worker subnet
+```bash
+oc get route hello -n afd-test
+```
- ```bash
- az network vnet subnet update \
- --disable-private-link-service-network-policies true \
- --name $WORKER_SUBNET_NAME \
- --resource-group $ARORG \
- --vnet-name $VNET_NAME
- ```
+Save the Route hostname:
-1. Create a private link service targeting the worker subnets
+```bash
+export APP_ROUTE=$(oc get route hello \
+ --namespace afd-test \
+ --output jsonpath='{.spec.host}')
- ```bash
- az network private-link-service create \
- --name $AROCLUSTER-pls \
- --resource-group $ARORG \
- --private-ip-address-version IPv4 \
- --private-ip-allocation-method Dynamic \
- --vnet-name $VNET_NAME \
- --subnet $WORKER_SUBNET_NAME \
- --lb-frontend-ip-configs $LBCONFIG_ID
+echo "$APP_ROUTE"
+```
- privatelink_id=$(az network private-link-service show -n $AROCLUSTER-pls -g $ARORG --query 'id' -o tsv)
- ```
+The value of `APP_ROUTE` must match `APP_FQDN`.
-## Create and Configure an instance of Azure Front Door
-1. Create a Front Door Instance
+## Identify the ARO Ingress Load Balancer
- ```bash
- az afd profile create \
- --resource-group $ARORG \
- --profile-name $AFD_NAME \
- --sku Premium_AzureFrontDoor
+Find the internal load balancer frontend associated with the worker subnet:
- afd_id=$(az afd profile show -g $ARORG --profile-name $AFD_NAME --query 'id' -o tsv)
- ```
+```bash
+for LB in $(az network lb list \
+ --resource-group "$ARO_INFRA_RESOURCE_GROUP" \
+ --query '[].name' \
+ -o tsv); do
+
+ az network lb frontend-ip list \
+ --resource-group "$ARO_INFRA_RESOURCE_GROUP" \
+ --lb-name "$LB" \
+ --query "[?subnet.id=='${WORKER_SUBNET_ID}'].{
+ loadBalancer:'$LB',
+ frontendName:name,
+ privateIPAddress:privateIPAddress,
+ subnet:subnet.id
+ }" \
+ -o table
+done
+```
-1. Create an endpoint for the ARO Internal Load Balancer
+Set the load balancer and frontend configuration names from the output:
- ```bash
- az afd endpoint create \
- --resource-group $ARORG \
- --enabled-state Enabled \
- --endpoint-name 'aro-ilb'$UNIQUEID \
- --profile-name $AFD_NAME
- ```
+```bash
+export INGRESS_LB=""
+export INGRESS_FRONTEND_CONFIG=""
+```
-1. Create a Front Door Origin Group that will point to the ARO Internal Loadbalancer
-
- ```bash
- az afd origin-group create \
- --origin-group-name 'afdorigin' \
- --probe-path '/' \
- --probe-protocol Http \
- --probe-request-type GET \
- --probe-interval-in-seconds 100 \
- --profile-name $AFD_NAME \
- --resource-group $ARORG \
- --probe-interval-in-seconds 120 \
- --sample-size 4 \
- --successful-samples-required 3 \
- --additional-latency-in-milliseconds 50
- ```
+Retrieve the ingress load balancer IP and frontend configuration ID:
-1. Create a Front Door Origin with the above Origin Group that will point to the ARO Internal Loadbalancer
-
- ```bash
- az afd origin create \
- --enable-private-link true \
- --private-link-resource $privatelink_id \
- --private-link-location $LOCATION \
- --private-link-request-message 'Private link service from AFD' \
- --weight 1000 \
- --priority 1 \
- --http-port 80 \
- --https-port 443 \
- --origin-group-name 'afdorigin' \
- --enabled-state Enabled \
- --host-name $LBCONFIG_IP \
- --origin-name 'afdorigin' \
- --profile-name $AFD_NAME \
- --resource-group $ARORG
- ```
+```bash
+export INGRESS_LB_IP=$(az network lb frontend-ip show \
+ --resource-group "$ARO_INFRA_RESOURCE_GROUP" \
+ --lb-name "$INGRESS_LB" \
+ --name "$INGRESS_FRONTEND_CONFIG" \
+ --query privateIPAddress \
+ -o tsv)
+
+export INGRESS_FRONTEND_CONFIG_ID=$(az network lb frontend-ip show \
+ --resource-group "$ARO_INFRA_RESOURCE_GROUP" \
+ --lb-name "$INGRESS_LB" \
+ --name "$INGRESS_FRONTEND_CONFIG" \
+ --query id \
+ -o tsv)
+
+echo "$INGRESS_LB_IP"
+```
-1. Approve the private link connection
+## Validate OpenShift Host-Based Routing
- ```bash
- privatelink_pe_id=$(az network private-link-service show -n $AROCLUSTER-pls -g $ARORG --query 'privateEndpointConnections[0].id' -o tsv)
+From a system that can reach the private ingress load balancer, request the load balancer IP without a matching Route hostname:
- az network private-endpoint-connection approve \
- --description 'Approved' \
- --id $privatelink_pe_id
- ```
+```bash
+curl -sS -D - -o /dev/null \
+ "http://${INGRESS_LB_IP}/"
+```
-1. Add your custom domain to Azure Front Door
+Expected result:
- ```bash
- az afd custom-domain create \
- --certificate-type ManagedCertificate \
- --custom-domain-name $AFD_MINE_CUSTOM_DOMAIN_NAME \
- --host-name $ARO_APP_FQDN \
- --minimum-tls-version TLS12 \
- --profile-name $AFD_NAME \
- --resource-group $ARORG
- ```
+```text
+HTTP/1.0 503 Service Unavailable
+```
-1. Create an Azure Front Door endpoint for your custom domain
+Repeat the request with the valid OpenShift Route hostname:
- ```bash
- az afd endpoint create \
- --resource-group $ARORG \
- --enabled-state Enabled \
- --endpoint-name 'aro-mine-'$UNIQUEID \
- --profile-name $AFD_NAME
- ```
+```bash
+curl -sS -D - \
+ -H "Host: ${APP_ROUTE}" \
+ "http://${INGRESS_LB_IP}/"
+```
-1. Add an Azure Front Door route for your custom domain
-
- ```bash
- az afd route create \
- --endpoint-name 'aro-mine-'$UNIQUEID \
- --forwarding-protocol HttpOnly \
- --https-redirect Disabled \
- --origin-group 'afdorigin' \
- --profile-name $AFD_NAME \
- --resource-group $ARORG \
- --route-name 'aro-mine-route' \
- --supported-protocols Http Https \
- --patterns-to-match '/*' \
- --custom-domains $AFD_MINE_CUSTOM_DOMAIN_NAME
- ```
+Expected result:
-1. Update DNS
+```text
+HTTP/1.1 200 OK
- Get a validation token from Front Door so Front Door can validate your domain
+Hello OpenShift!
+```
- ```bash
- afdToken=$(az afd custom-domain show \
- --resource-group $ARORG \
- --profile-name $AFD_NAME \
- --custom-domain-name $AFD_MINE_CUSTOM_DOMAIN_NAME \
- --query "validationProperties.validationToken"
- -o tsv)
- ```
+This establishes the expected router behavior:
-1. Create a DNS Zone
+```text
+Ingress IP with IP Host header -> 503
+Ingress IP with matching Route Host -> 200
+```
- ```bash
- az network dns zone create -g $ARORG -n $DOMAIN
- ```
+## Create a Dedicated Private Link Service Subnet
- >You will need to configure your nameservers to point to azure. The output of running this zone create will show you the nameservers for this record that you will need to set up within your domain registrar.
+List the virtual network address space and existing subnets:
- Create a new text record in your DNS server
+```bash
+az network vnet show \
+ --resource-group "$VNET_RESOURCE_GROUP" \
+ --name "$VNET_NAME" \
+ --query '{
+ addressSpace:addressSpace.addressPrefixes,
+ subnets:subnets[].{
+ name:name,
+ prefix:addressPrefix,
+ prefixes:addressPrefixes
+ }
+ }' \
+ -o yaml
+```
- ```bash
- az network dns record-set txt add-record -g $ARORG -z $DOMAIN -n _dnsauth.$(echo $ARO_APP_FQDN | sed 's/\..*//') --value $afdToken --record-set-name _dnsauth.$(echo $ARO_APP_FQDN | sed 's/\..*//')
- ```
+Confirm that `PLS_SUBNET_PREFIX` is unused and falls within the virtual network address space.
-1. Check if the domain has been validated:
- >Note this can take several hours
- Your FQDN will not resolve until Front Door validates your domain.
+Create a dedicated subnet for the Private Link Service:
- ```bash
- az afd custom-domain list -g $ARORG --profile-name $AFD_NAME --query "[? contains(hostName, '$ARO_APP_FQDN')].domainValidationState"
- ```
+```bash
+az network vnet subnet create \
+ --resource-group "$VNET_RESOURCE_GROUP" \
+ --vnet-name "$VNET_NAME" \
+ --name "$PLS_SUBNET_NAME" \
+ --address-prefixes "$PLS_SUBNET_PREFIX" \
+ --disable-private-link-service-network-policies true
+```
-1. Add a CNAME record to DNS
+Verify the subnet:
- Get the Azure Front Door endpoint:
+```bash
+az network vnet subnet show \
+ --resource-group "$VNET_RESOURCE_GROUP" \
+ --vnet-name "$VNET_NAME" \
+ --name "$PLS_SUBNET_NAME" \
+ --query '{
+ prefix:addressPrefix,
+ privateLinkServiceNetworkPolicies:privateLinkServiceNetworkPolicies
+ }' \
+ -o yaml
+```
- ```bash
- afdEndpoint=$(az afd endpoint show -g $ARORG --profile-name $AFD_NAME --endpoint-name aro-mine-$UNIQUEID --query "hostName" -o tsv)
- ```
+Expected output:
- Create a cname record for the application
+```yaml
+privateLinkServiceNetworkPolicies: Disabled
+```
- ```bash
- az network dns record-set cname set-record -g $ARORG -z $DOMAIN \
- -n $(echo $ARO_APP_FQDN | sed 's/\..*//') -z $DOMAIN -c $afdEndpoint
- ```
-## Deploy an application
-Now the fun part, let's deploy an application!
-We will be deploying a Java based application called [microsweeper](https://github.com/redhat-mw-demos/microsweeper-quarkus/tree/ARO). This is an application that runs on OpenShift and uses a PostgreSQL database to store scores. With ARO being a first class service on Azure, we will create an Azure Database for PostgreSQL service and connect it to our cluster with a private endpoint.
+{{< alert >}}
+Use a dedicated subnet for the Private Link Service rather than changing network policies on the ARO control-plane or worker subnet.
+{{< /alert >}}
+
+Retrieve the subnet resource ID:
+
+```bash
+export PLS_SUBNET_ID=$(az network vnet subnet show \
+ --resource-group "$VNET_RESOURCE_GROUP" \
+ --vnet-name "$VNET_NAME" \
+ --name "$PLS_SUBNET_NAME" \
+ --query id \
+ -o tsv)
+```
-1. Create a Azure Database for PostgreSQL servers service
+## Create the Azure Private Link Service
- ```bash
- az postgres server create --name microsweeper-database --resource-group $ARORG --location $LOCATION --admin-user quarkus --admin-password r3dh4t1! --sku-name GP_Gen5_2
+Create a Private Link Service attached to the ARO ingress load balancer frontend:
- POSTGRES_ID=$(az postgres server show -n microsweeper-database -g $ARORG --query 'id' -o tsv)
- ```
+```bash
+az network private-link-service create \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --name "$PLS_NAME" \
+ --location "$LOCATION" \
+ --lb-frontend-ip-configs "$INGRESS_FRONTEND_CONFIG_ID" \
+ --subnet "$PLS_SUBNET_ID" \
+ --private-ip-address-version IPv4 \
+ --private-ip-allocation-method Dynamic
+```
+Retrieve its resource ID:
-1. Create a private endpoint connection for the database
-
- ```bash
- az network vnet subnet create \
- --resource-group $ARORG \
- --vnet-name $VNET_NAME \
- --name $PRIVATEENDPOINTSUBNET_NAME \
- --address-prefixes $PRIVATEENDPOINTSUBNET_PREFIX \
- --disable-private-endpoint-network-policies true
-
- az network private-endpoint create \
- --name 'postgresPvtEndpoint' \
- --resource-group $ARORG \
- --vnet-name $VNET_NAME \
- --subnet $PRIVATEENDPOINTSUBNET_NAME \
- --private-connection-resource-id $POSTGRES_ID \
- --group-id 'postgresqlServer' \
- --connection-name 'postgresdbConnection'
- ```
-1. Create and configure a private DNS Zone for the Postgres database
+```bash
+export PLS_ID=$(az network private-link-service show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --name "$PLS_NAME" \
+ --query id \
+ -o tsv)
+```
- ```bash
- az network private-dns zone create \
- --resource-group $ARORG \
- --name 'privatelink.postgres.database.azure.com'
+Verify the Private Link Service:
- az network private-dns link vnet create \
- --resource-group $ARORG \
- --zone-name 'privatelink.postgres.database.azure.com' \
- --name 'PostgresDNSLink' \
- --virtual-network $VNET_NAME \
- --registration-enabled false
+```bash
+az network private-link-service show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --name "$PLS_NAME" \
+ --query '{
+ provisioningState:provisioningState,
+ frontendConfigurations:loadBalancerFrontendIpConfigurations[].id,
+ natConfigurations:ipConfigurations[].{
+ name:name,
+ privateIPAddress:privateIPAddress,
+ allocationMethod:privateIPAllocationMethod,
+ subnet:subnet.id
+ }
+ }' \
+ -o yaml
+```
+
+## Create Azure Front Door Premium in the Azure Portal
- az network private-endpoint dns-zone-group create \
- --resource-group $ARORG \
- --name 'PostgresDb-ZoneGroup' \
- --endpoint-name 'postgresPvtEndpoint' \
- --private-dns-zone 'privatelink.postgres.database.azure.com' \
- --zone-name 'postgresqlServer'
+This guide uses the Azure Portal to create the Azure Front Door resources. In validation, the equivalent Azure CLI resources were accepted by the control plane but remained at `deploymentStatus: NotStarted`, and the endpoint returned `404 CONFIG_NOCACHE`. Creating the Front Door profile and route through the Portal successfully published the configuration to the Front Door data plane.
- NETWORK_INTERFACE_ID=$(az network private-endpoint show --name postgresPvtEndpoint --resource-group $ARORG --query 'networkInterfaces[0].id' -o tsv)
+The Portal workflow follows this order:
- POSTGRES_IP=$(az resource show --ids $NETWORK_INTERFACE_ID --api-version 2019-04-01 --query 'properties.ipConfigurations[0].properties.privateIPAddress' -o tsv)
+```text
+Front Door profile
+ -> Endpoint
+ -> Route
+ -> Origin group
+ -> Origin
+```
- az network private-dns record-set a create --name $UNIQUEID-microsweeper-database --zone-name privatelink.postgres.database.azure.com --resource-group $ARORG
+You complete the innermost resource first, then return through each parent form:
- az network private-dns record-set a add-record --record-set-name $UNIQUEID-microsweeper-database --zone-name privatelink.postgres.database.azure.com --resource-group $ARORG -a $POSTGRES_IP
- ```
+```text
+Add origin
+ -> Add origin group
+ -> Add route
+ -> Create Front Door profile
+```
-1. Create a postgres database that will contain scores for the minesweeper application
+### Create the Front Door Profile
- ```bash
- az postgres db create \
- --resource-group $ARORG \
- --name score \
- --server-name microsweeper-database
- ```
+1. In the Azure Portal, search for **Front Doors**.
-## Deploy the [minesweeper application](https://github.com/rh-mobb/aro-workshop-app.git)
+2. Select **Create**.
-1. Clone the git repository
+3. Select **Azure Front Door**.
- ```bash
- git clone https://github.com/rh-mobb/aro-workshop-app.git
- ```
+4. Select **Custom create**.
-1. change to the root directory
+5. Configure the profile:
- ```bash
- cd aro-workshop-app
+ ```text
+ Subscription:
+ Resource group:
+ Name:
+ Tier: Azure Front Door Premium
```
-1. Ensure Java 1.8 is set at your Java version
+ Azure Front Door Premium is required for Private Link origins.
- ```bash
- mvn --version
- ```
+6. Do not select **Review + Create** yet. Continue to the endpoint configuration on the same profile creation page.
- Look for Java version - 1.8XXXX
- if not set to Java 1.8 you will need to set your JAVA_HOME variable to Java 1.8 you have installed. To find your java versions run:
+### Add an Endpoint
- ```bash
- java -version
- ```
+1. In the **Endpoint** tab or section, select **Add an endpoint**.
- then export your JAVA_HOME variable
+2. Configure the endpoint:
- ```bash
- export JAVA_HOME=`/usr/libexec/java_home -v 1.8.0_332`
+ ```text
+ Endpoint name:
+ Status: Enabled
```
-1. Log into your openshift cluster
- > Before you deploy your application, you will need to be connected to a private network that has access to the cluster.
+3. Add the endpoint.
+
+The new endpoint appears within the Front Door profile creation page.
- A great way to establish this connectity is with a VPN connection. Follow this [guide](../vpn/) to setup a VPN connection with your Azure account.
+### Add a Route
- ```bash
- kubeadmin_password=$(az aro list-credentials --name $AROCLUSTER --resource-group $ARORG --query kubeadminPassword --output tsv)
+1. Within the endpoint that you just created, select **Add a route**.
- apiServer=$(az aro show -g $ARORG -n $AROCLUSTER --query apiserverProfile.url -o tsv)
+2. Configure the route:
- oc login $apiServer -u kubeadmin -p $kubeadmin_password
+ ```text
+ Route name: aro-route
+ Domains:
+ Patterns to match: /*
+ Accepted protocols: HTTP and HTTPS
+ Redirect HTTP to HTTPS: Disabled
+ Forwarding protocol: HTTP only
+ Caching: Disabled
+ Link to default domain: Enabled
+ Status: Enabled
```
-1. Create a new OpenShift Project
+3. Do not add the route yet. From within the route form, select **Add a new origin group**.
- ```bash
- oc new-project minesweeper
- ```
+### Add an Origin Group
-1. add the openshift extension to quarkus
+1. In the origin group form, configure:
- ```bash
- quarkus ext add openshift
+ ```text
+ Name: aro-origin-group
+ Session affinity: Disabled
+ Health probes: Enabled
+ Path: /
+ Protocol: HTTP
+ Request type: HEAD
+ Interval: 30 seconds
+ Sample size: 4
+ Successful samples required: 3
+ Additional latency: 50 milliseconds
```
-1. Edit microsweeper-quarkus/src/main/resources/application.properties
+2. Do not add the origin group yet. From within the origin group form, select **Add an origin**.
- Make sure your file looks like the one below, changing the IP address on line 3 to the private ip address of your postgres instance.
+### Add the ARO Origin
- To find your Postgres private IP address run the following commands:
+Configure the origin:
- ```bash
- NETWORK_INTERFACE_ID=$(az network private-endpoint show --name postgresPvtEndpoint --resource-group $ARORG --query 'networkInterfaces[0].id' -o tsv)
+```text
+Name: aro-ingress
+Origin type: Custom
+Host name:
+Origin host header:
+HTTP port: 80
+HTTPS port: 443
+Priority: 1
+Weight: 1000
+Status: Enabled
+Enable Private Link: Yes
+Private Link region:
+Private Link target:
+Target subresource: Leave blank
+Request message: Azure Front Door access to private ARO ingress
+```
- az resource show --ids $NETWORK_INTERFACE_ID --api-version 2019-04-01 --query 'properties.ipConfigurations[0].properties.privateIPAddress' -o tsv
- ```
+The two most important values are:
- Sample microsweeper-quarkus/src/main/resources/application.properties
+```text
+Host name:
+
- ```
- # Database configurations
- %prod.quarkus.datasource.db-kind=postgresql
- %prod.quarkus.datasource.jdbc.url=jdbc:postgresql://10.1.6.9:5432/score
- %prod.quarkus.datasource.jdbc.driver=org.postgresql.Driver
- %prod.quarkus.datasource.username=quarkus@microsweeper-database
- %prod.quarkus.datasource.password=r3dh4t1!
- %prod.quarkus.hibernate-orm.database.generation=drop-and-create
- %prod.quarkus.hibernate-orm.database.generation=update
-
- # OpenShift configurations
- %prod.quarkus.kubernetes-client.trust-certs=true
- %prod.quarkus.kubernetes.deploy=true
- %prod.quarkus.kubernetes.deployment-target=openshift
- %prod.quarkus.openshift.build-strategy=docker
- ```
+Origin host header:
+
+```
-1. Build and deploy the quarkus application to OpenShift
+For example:
- ```bash
- quarkus build --no-tests
- ```
+```text
+Host name:
+10.0.3.254
-1. Create a route to your custom domain
- Change the snippet below replacing your hostname for the host:
-
- ```bash
- cat << EOF | oc apply -f -
- apiVersion: route.openshift.io/v1
- kind: Route
- metadata:
- labels:
- app.kubernetes.io/name: microsweeper-appservice
- app.kubernetes.io/version: 1.0.0-SNAPSHOT
- app.openshift.io/runtime: quarkus
- name: microsweeper-appservice
- namespace: minesweeper
- spec:
- host: minesweeper.aro.kmobb.com
- to:
- kind: Service
- name: microsweeper-appservice
- weight: 100
- targetPort:
- port: 8080
- wildcardPolicy: None
- EOF
- ```
+Origin host header:
+hello-afd-test.apps.example.westus2.aroapp.io
+```
+
+The origin host header must match an existing OpenShift Route. Do not set it to the load balancer IP. Otherwise, the OpenShift ingress router cannot match the request or health probe to an application Route and returns `503 Application is not available`.
+
+1. Select **Add** to finish the origin.
+
+You are returned to the origin group form, where the new origin should now be listed.
+
+### Finish the Origin Group
+
+1. Review the health-probe settings and confirm that the new `aro-ingress` origin is listed.
+2. Select **Add** to finish the origin group.
+
+You are returned to the route form, where `aro-origin-group` should now be selected as the route’s origin group.
-1. Check the dns settings of your application.
- > notice that the application URL is routed through Azure Front Door at the edge. The only way this application that is running on your cluster can be access is through Azure Front Door which is connected to your cluster through a private endpoint.
+### Finish the Route
- ```bash
- nslookup $ARO_APP_FQDN
+1. Review the route configuration.
+
+2. Confirm:
+
+ ```text
+ Domain: Default azurefd.net endpoint
+ Pattern: /*
+ Accepted protocols: HTTP and HTTPS
+ Forwarding protocol: HTTP only
+ Origin group: aro-origin-group
+ Link to default domain: Enabled
```
- sample output:
+3. Select **Add** to finish the route.
+
+You are returned to the Front Door profile creation page. The endpoint should now show the nested route and origin group configuration.
+
+### Review and Create the Front Door Profile
+
+1. Review the profile and endpoint configuration.
+2. Confirm that the hierarchy resembles:
+
+ ```text
+
+ ->
+ -> aro-route
+ -> aro-origin-group
+ -> aro-ingress
```
- Server: 2600:1700:850:d220::1
- Address: 2600:1700:850:d220::1#53
-
- Non-authoritative answer:
- minesweeper.aro.kmobb.com canonical name = aro-mine-13947-dxh0ahd7fzfyexgx.z01.azurefd.net.
- aro-mine-13947-dxh0ahd7fzfyexgx.z01.azurefd.net canonical name = star-azurefd-prod.trafficmanager.net.
- star-azurefd-prod.trafficmanager.net canonical name = dual.part-0013.t-0009.t-msedge.net.
- dual.part-0013.t-0009.t-msedge.net canonical name = part-0013.t-0009.t-msedge.net.
- Name: part-0013.t-0009.t-msedge.net
- Address: 13.107.213.41
- Name: part-0013.t-0009.t-msedge.net
- Address: 13.107.246.41
+
+3. Select **Review + create**, if shown.
+
+4. Select **Create** to deploy the Front Door profile.
+
+After deployment, approve the new managed private endpoint request on the Azure Private Link Service before testing the Front Door endpoint.
+
+
+
+
+
+
+### Approve the Azure Front Door Private Endpoint Connection
+
+After the Front Door profile is created, Azure Front Door creates a managed private endpoint request against the Private Link Service.
+
+1. In the Azure Portal, search for **Private Link services**.
+
+2. Open the Private Link Service created earlier.
+
+3. Select **Private endpoint connections**.
+
+4. Locate the new connection with status **Pending**.
+
+5. Select the pending connection.
+
+6. Select **Approve**.
+
+7. Enter an optional approval message, such as:
+
+ ```text
+ Approved for Azure Front Door
```
-## Test the application
-Point your broswer to your domain!!
-
+8. Confirm that the connection status changes to **Approved**.
+
+A new Azure Front Door profile creates a new managed private endpoint request, even when it uses an existing Private Link Service. Approve each new request before testing the Front Door endpoint.
+
+Allow several minutes for the approved private connection and Front Door configuration to propagate.
+
+## Validate the Default Front Door Endpoint
+
+Copy the default endpoint hostname from:
+
+```text
+Azure Front Door profile
+ -> Front Door manager
+ -> Endpoint
+```
+
+Set it as an environment variable:
+
+```bash
+export AFD_ENDPOINT_HOSTNAME=".azurefd.net"
+```
+
+Test the endpoint:
+
+```bash
+curl -sS \
+ -w '\nHTTP status: %{http_code}\n' \
+ "http://${AFD_ENDPOINT_HOSTNAME}/"
+```
+
+Expected output:
+
+```text
+Hello OpenShift!
+
+HTTP status: 200
+```
+
+Run repeated tests:
+
+```bash
+for i in {1..6}; do
+ date
+ curl -sS -o /dev/null \
+ -w 'HTTP %{http_code}\n' \
+ "http://${AFD_ENDPOINT_HOSTNAME}/"
+ sleep 30
+done
+```
+
+All requests should return `HTTP 200`.
+
+The successful request flow is:
+
+```text
+Client Host:
+.azurefd.net
+
+Azure Front Door origin host header:
+
+
+ARO ingress router:
+Matches the application Route and returns 200
+```
+
+In Azure Portal, check the **Origin Health Percentage** metric for the Front Door profile after the configuration has propagated.
+
+## Configure a Custom Domain
+
+Create an Azure Front Door custom-domain object:
+
+```bash
+export AFD_CUSTOM_DOMAIN_NAME=$(echo "$APP_FQDN" | tr '.' '-')
+
+az afd custom-domain create \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --profile-name "$AFD_PROFILE" \
+ --custom-domain-name "$AFD_CUSTOM_DOMAIN_NAME" \
+ --host-name "$APP_FQDN" \
+ --certificate-type ManagedCertificate \
+ --minimum-tls-version TLS12
+```
+
+Retrieve the DNS validation token:
+
+```bash
+export AFD_VALIDATION_TOKEN=$(az afd custom-domain show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --profile-name "$AFD_PROFILE" \
+ --custom-domain-name "$AFD_CUSTOM_DOMAIN_NAME" \
+ --query validationProperties.validationToken \
+ -o tsv)
+```
+
+Create the required `_dnsauth` TXT record in the authoritative DNS zone for `APP_FQDN`.
+
+The exact command depends on the DNS provider. For Azure DNS, set the DNS zone and relative record name:
+
+```bash
+export DNS_RESOURCE_GROUP=""
+export DNS_ZONE=""
+export DNS_RECORD_NAME=""
+```
+
+For example, if `APP_FQDN` is `hello.apps.example.com` and the DNS zone is `example.com`, the relative record name is `hello.apps`.
+
+Create the validation record:
+
+```bash
+az network dns record-set txt add-record \
+ --resource-group "$DNS_RESOURCE_GROUP" \
+ --zone-name "$DNS_ZONE" \
+ --record-set-name "_dnsauth.${DNS_RECORD_NAME}" \
+ --value "$AFD_VALIDATION_TOKEN"
+```
+
+Check the validation state:
+
+```bash
+az afd custom-domain show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --profile-name "$AFD_PROFILE" \
+ --custom-domain-name "$AFD_CUSTOM_DOMAIN_NAME" \
+ --query domainValidationState \
+ -o tsv
+```
+
+After the domain is validated, associate it with the existing route in the Portal:
+
+```text
+Azure Front Door profile
+ -> Front Door manager
+ -> Endpoint
+ -> aro-route
+ -> Domains
+```
+
+Select the validated custom domain, retain the default Front Door domain while testing, and save the route.
+
+Create a CNAME record that points the application hostname to the Azure Front Door endpoint:
+
+```bash
+az network dns record-set cname set-record \
+ --resource-group "$DNS_RESOURCE_GROUP" \
+ --zone-name "$DNS_ZONE" \
+ --record-set-name "$DNS_RECORD_NAME" \
+ --cname "$AFD_ENDPOINT_HOSTNAME"
+```
+
+Allow time for DNS and Front Door certificate provisioning to complete.
+
+## Validate the Configuration
+
+Confirm the OpenShift Route:
+
+```bash
+oc get route hello -n afd-test
+```
+
+Confirm the Azure Front Door origin:
+
+```bash
+az afd origin show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --profile-name "$AFD_PROFILE" \
+ --origin-group-name "$AFD_ORIGIN_GROUP" \
+ --origin-name "$AFD_ORIGIN" \
+ --query '{
+ hostName:hostName,
+ originHostHeader:originHostHeader,
+ provisioningState:provisioningState
+ }' \
+ -o yaml
+```
+
+Test the custom application hostname:
+
+```bash
+curl -sS \
+ -w '\nHTTP status: %{http_code}\n' \
+ "https://${APP_FQDN}/"
+```
+
+Expected output:
+
+```text
+Hello OpenShift!
+
+HTTP status: 200
+```
+
+Run repeated tests:
-## Clean up
-To clean up everything you created, simply delete the resource group
+```bash
+for i in {1..6}; do
+ date
+ curl -sS -o /dev/null \
+ -w 'HTTP %{http_code}\n' \
+ "https://${APP_FQDN}/"
+ sleep 30
+done
+```
+
+All requests should return `HTTP 200`.
+
+In Azure Portal, check the **Origin Health Percentage** metric for the Front Door profile. A healthy origin should report successful probes after the configuration has propagated.
+
+## Troubleshooting
+
+### Azure Front Door returns `404 CONFIG_NOCACHE`
+
+A response similar to the following usually means Front Door did not match an active frontend route:
+
+```text
+HTTP/1.1 404 Not Found
+X-Cache: CONFIG_NOCACHE
+```
+
+Verify:
+
+* The route is enabled.
+* `linkToDefaultDomain` is enabled when testing the default `azurefd.net` hostname.
+* The route pattern includes `/*`.
+* The incoming hostname is associated with the route.
+* The Front Door configuration has had time to propagate.
+
+Do not override the request `Host` header with an unregistered custom domain when testing the default `azurefd.net` endpoint. Front Door performs frontend route matching before it contacts the origin.
+
+### OpenShift returns `503 Application is not available`
+
+This response proves that Front Door reached the OpenShift ingress router, but the router could not match the request to a Route.
+
+Compare the OpenShift Route hostname and Front Door origin host header:
+
+```bash
+oc get route hello \
+ --namespace afd-test \
+ --output jsonpath='{.spec.host}{"\n"}'
+
+az afd origin show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --profile-name "$AFD_PROFILE" \
+ --origin-group-name "$AFD_ORIGIN_GROUP" \
+ --origin-name "$AFD_ORIGIN" \
+ --query originHostHeader \
+ -o tsv
+```
+
+These values must match exactly.
+
+Validate the Route directly:
```bash
-az group delete -g $ARORG
+curl -sS -D - \
+ -H "Host: ${APP_ROUTE}" \
+ "http://${INGRESS_LB_IP}/"
```
+
+### Health probes return `503`
+
+Ensure that:
+
+* The origin host header is set to the application Route hostname.
+* The probe path exists.
+* The application returns `200 OK` for the selected probe method.
+* `HEAD` is replaced with `GET` when the application does not support `HEAD`.
+
+Test `HEAD` directly:
+
+```bash
+curl -sS -I \
+ -H "Host: ${APP_ROUTE}" \
+ "http://${INGRESS_LB_IP}/"
+```
+
+Only `200 OK` is considered a successful Azure Front Door health probe response.
+
+### Private Link connection is not working
+
+Check the Private Link Service connection:
+
+```bash
+az network private-link-service show \
+ --resource-group "$ARO_RESOURCE_GROUP" \
+ --name "$PLS_NAME" \
+ --query 'privateEndpointConnections[].{
+ status:privateLinkServiceConnectionState.status,
+ description:privateLinkServiceConnectionState.description
+ }' \
+ -o yaml
+```
+
+The status must be `Approved`.
+
+A new Azure Front Door profile creates a new managed private endpoint request, even when it uses the same Private Link Service. Approve each new request.
+
+### Direct access to the ingress IP times out
+
+The ingress load balancer is private. Confirm that the system running `curl` still has a valid network path into the ARO virtual network, such as VPN, ExpressRoute, jump host access, or an active SSH tunnel.
+
+A timeout from the client does not necessarily mean the OpenShift Route is unavailable.
+
+## Clean Up
+
+Use the Azure Portal to remove the Azure Front Door resources created in this guide.
+
+### Delete the Azure Front Door Profile
+
+1. In the Azure Portal, search for **Front Doors**.
+2. Open the Azure Front Door profile created for this guide.
+3. On the profile **Overview** page, select **Delete**.
+4. Click 'Yes' if prompted.
+5. Confirm the deletion.
+6. This may take some time. Wait for the Azure notification to report that the deletion completed successfully.
+
+Deleting the profile also removes the Front Door resources contained within it, including:
+
+```text
+Endpoint
+Route
+Origin group
+Origin
+```
+
+It does not delete the ARO cluster, the ARO internal load balancer, the Private Link Service, or the dedicated Private Link Service subnet.
+
+### Check the Private Link Service Connection
+
+After the Front Door profile is deleted:
+
+1. In the Azure Portal, search for **Private Link services**.
+2. Open the Private Link Service created for this guide.
+3. Select **Private endpoint connections**.
+4. Confirm that the Azure Front Door managed private endpoint connection has been removed.
+
+If a disconnected or stale connection remains, delete it only after confirming that the Front Door profile has been removed.
+
+### Delete the Private Link Service
+
+1. From the Private Link Service overview page, select **Delete**.
+2. Confirm the deletion.
+3. Wait for the deletion to complete.
+
+### Delete the Dedicated Private Link Service Subnet
+
+1. In the Azure Portal, open the virtual network used by the ARO cluster.
+2. Select **Subnets**.
+3. Select the dedicated subnet created for the Private Link Service.
+4. Select **Delete**.
+5. Confirm the deletion.
+
+Do not delete the ARO control-plane or worker subnets.
+
+### Delete the Test Application
+
+Delete the OpenShift test project:
+
+```bash
+oc delete project afd-test
+```
+
+### Delete DNS Records
+
+Delete any DNS records created for the test application, including:
+
+```text
+CNAME record
+_dnsauth TXT record
+```
+
+Retain the DNS zone if it is used by other applications.
+
+## Additional Resources
+
+* [Secure your origin with Private Link in Azure Front Door Premium](https://learn.microsoft.com/en-us/azure/frontdoor/private-link)
+* [Origins and origin groups in Azure Front Door](https://learn.microsoft.com/en-us/azure/frontdoor/origin)
+* [Azure Front Door health probes](https://learn.microsoft.com/en-us/azure/frontdoor/health-probes)
+* [Create an Azure Private Link Service using Azure CLI](https://learn.microsoft.com/en-us/azure/private-link/create-private-link-service-cli)
+* [Azure Red Hat OpenShift documentation](https://learn.microsoft.com/en-us/azure/openshift/)
\ No newline at end of file