Azure Container Registry: Advanced Network with Azure Application Gateway

Posted by  Bin Du on Tuesday, April 18, 2023

Control network access

I often got the question regarding how to secure the public access to Azure Container Registry (ACR). ACR already provides several features for advanced network access control. For example, you can disable public access and only allow the requests from certain Public IP ranges. However, the built-in network features have some limits like it only allows 100 IP network rules. If you need to filter the requests based on geo-location, Azure Application Gateway (AG) is a viable option.

Azure Application Gateway Web Application Firewall (WAF) v2 not only allows you to create custom rules like Geomatch but also protects the ACR from common vulnerabilities and exploits. You can also set up custom domain for the original ACR instance (NOTE: ACR has a preview feature to set up custom domain for your registry).

Put into action

To demonstrate the end-to-end, I am going to build a public registry registry.writeinseattle.com on top of a private ACR testpeacrwestus3.azurecr.io. I will push new image to testpeacrwestus3.azurecr.io from my local network. The clients from United States will be able to pull the image from registry.writeinseattle.com but the requests from other location will be blocked. The diagram shows the high-level network architecture.

                     ┌─────────────────────────────────────────┐
                     │                                         │
                     │    Virtual Network                      │
                     │                                         │
                     │   ┌────────────────────┐                │
                     │   │                    │                │
   ┌───────────┐     │   │                    │                │
   │           │     │   │ Application        │                │     ┌────────────────────────┐
   │ Internet  ├─────┼───► Gateway            │ ┌────────────┐ │     │                        │
   │           │     │   │       │            │ │            │ │     │                        │
   └───────────┘     │   │       └────────────┼─► ACR        │ │     │Azure Container Registry│
                     │   │                    │ │ Private    ├─┼─────►                        │
                     │   └────────────────────┘ │ Endpoint   │ │     │                        │
                     │                          └────────────┘ │     └────────────────────────┘
                     │                                         │
                     └─────────────────────────────────────────┘

To start, I first create the ACR testpeacrwestus3.azurecr.io in Azure westus3 region and disable the public access. I also enable anonymous pull access on the registry. I then create a virtual network with two subnets, one for ACR private endpoint and the other for AG. Once I create the ACR private endpoint in the subnet, I get two endpoints https://testpeacrwestus3.azurecr.io (a.k.a login endpoint) and https://testpeacrwestus3.westus3.data.azurecr.io (a.k.a data endpoint).

Next, I create the AG (WAFv2) in another subnet. To allow clients to pull image from registry.writeinseattle.com, I need to set up two HTTPS listeners on AG. The listener of https://registry.writeinseattle.com forwards the requests to the backend pool targeting https://testpeacrwestus3.azurecr.io. The listener of https://registry.westus3.data.writeinseattle.com forwards the requests to the backend pool targeting https://testpeacrwestus3.westus3.data.azurecr.io. For Health probes, choose HTTPS Protocol and /health PATH.

After setting up the routing rules, I need to configure two response rewrite rules to redirect the client to the desired AG endpoints.

  1. Registry clients follow the auth flow to parse WWW-Authenticate header from 401 Unauthorized response to figure out the request payload to acquire the token. The header value is something like Www-Authenticate: Bearer realm="https://testpeacrwestus3.azurecr.io/oauth2/token",service="testpeacrwestus3.azurecr.io",scope="repository:hello-world:pull". I need to create a rule to rewrite realm="https://testpeacrwestus3.azurecr.io/oauth2/token" to realm="https://registry.writeinseattle.com/oauth2/token". Here is the sample rule setting.

    If: Service variable/http_status equal (=) 401
    And If: HTTP header/Response Header/WWW-Authentication equal (=) (Bearer realm=.https:\/\/)testpeacrwestus3\.azurecr\.io(.*)$
    Then: Response Header Set WWW-Authentication {http_resp_WWW-Authenticate_1}registry.writeinseattle.com{http_resp_WWW-Authenticate_2}
    
  2. When registry clients request to download image layers, ACR returns 307 Redirect response that includes location header to redirect clients to the data endpoint. I need to create a rule to rewrite location=https://testpeacrwestus3.westus3.data.azurecr.io/... to location=https://registry.westus3.data.writeinseattle.com/.... Here is the sample rule setting.

    If: Service variable/http_status equal (=) 307
    And If: HTTP header/Response Header/Location equal (=) (https:\/\/)testpeacrwestus3\.westus3\.data\.azurecr\.io(.*)$
    Then: Response Header Set Location {http_resp_Location_1}registry.westus3.data.writeinseattle.com{http_resp_Location_2}
    

Finally, I add the Geomatch rule in Application Gateway WAF policy to only allow clients from United States to pull image from registry.writeinseattle.com (NOTE: the default mode for new created policy is detection, I need to remember to switch to prevention mode to put into effect).

Conclusion

If you are in United States, you can try to pull a sample hello-world image docker pull registry.writeinseattle.com/hello-world:latest. Hope you find the article is useful =).