﻿# 存取 Azure Container Registry (ACR) 的多種認證授權方法

[Azure Container Registry](https://azure.microsoft.com/en-us/services/container-registry/?WT.mc_id=AZ-MVP-4015686) (ACR) 是 Azure 雲端平台非常重要的基礎建設，不但可以用來儲存 Docker Images，也可以用來儲存 [Helm Charts](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-helm-repos?WT.mc_id=AZ-MVP-4015686) 在上面。除此之外，企業所需的身份認證授權功能也相當完整，但只要扯到認證授權，事情就會複雜許多。這篇文章我將深入探討 ACR 各種不同的認證授權方法，幫助企業導入 ACR 使用。

### 關於操作 ACR 的多種認證方法

我是從 [Authenticate with an Azure container registry](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication?WT.mc_id=AZ-MVP-4015686) 這頁開始看起，其內容相當詳細，唯獨缺少 REST API 的相關說明。

> 如果你想透過 Docker CLI (`docker login`) 或 [AKS](https://learn.microsoft.com/en-us/azure/aks/cluster-container-registry-integration?toc=/azure/container-registry/toc.json&bc=/azure/container-registry/breadcrumb/toc.json&WT.mc_id=AZ-MVP-4015686) 來存取 ACR 上面的資源，看這篇文章就可以解決所有問題。

這篇文章提到多種認證方法：

1.  [Admin account](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication?WT.mc_id=DT-MVP-4015686#admin-account)
    
    因為一個 ACR 只能有一個預設的「管理者帳號」，對於**獨立開發者**（只有一位開發人員）來說非常方便，但是對一間公司有**多位開發者**要共用一個 ACR 的情境來說，就顯的比較不實用了。
    
    請注意：ACR 預設並沒有啟用 **Admin user** 帳號，如果要用的話，請記得要先啟用才行！
    
    ![ACR 預設並沒有啟用 Admin user 帳號](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/624d6771683a8b27b9a4-95567438-86808680-0a55-11eb-99bc-7262cbf0f6d5.png)
    
    你也可以用 Azure CLI 來啟用 **Admin user** 帳號：
    
    ```bash
    az acr update -n <acrName> --admin-enabled true
    ```
    
    啟用 **Admin user** 之後，你就會得到**一個 Username** 與**兩個 Password** (任選一個皆可)：
    
    ![啟用 Admin user 之後，你就會得到一個 Username 與兩個 Password (任選一個皆可)](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/265aa382de0ef0363136-95567615-c6476e00-0a55-11eb-94d8-fd49e5000f51.png)
    
    然後就可以輕鬆的使用 `docker login <Login server>` 來進行登入！
    
2.  [Individual login with Azure AD](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication?WT.mc_id=DT-MVP-4015686#individual-login-with-azure-ad)
    
    我們公司就是採用這種認證方式，因為大家都有 Microsoft 365 帳號（因為背後是使用 Azure AD 目錄），所以可以直接透過 Azure AD 進行授權。如果大家想要存取 ACR 的話，只要輸入以下命令就可以登入完成：
    
    ```bash
    az login
    az account set -s <subscriptionId>
    az acr login --name <acrName>
    ```
    
    當你執行 `az acr login` 命令的時候，預設就隱含包括 `docker login` 登入，所以如果你沒有事先安裝 Docker 的話，是無法執行的。當然，你在執行完 `az acr login` 登入之後，不用再執行一次 `docker login <Login server>` 登入！
    
    > 注意：使用 `az acr login` 登入 ACR 的話，有效期間**只有 3 小時**而已！當時間一過，就無法再進行 `docker push` 與 `docker pull` 囉，你必須再次執行 `az acr login` 才能繼續使用。
    
    如果你目前執行 Azure CLI 的電腦並沒有安裝 Docker，例如在 [Azure Cloud Shell](https://azure.microsoft.com/zh-tw/features/cloud-shell/?WT.mc_id=AZ-MVP-4015686) 執行命令。那麼你可能會想先取得 `docker login` 的臨時帳號密碼，讓你在其他電腦可以執行 `docker login` 登入，此時你可以透過以下命令來取得帳號密碼：
    
    ```bash
    az acr login --name <acrName> --expose-token
    ```
    
    這個命令會取得以下 JSON 結果：
    
    ```json
    {
      "accessToken": "eyJhbGciOiJSUzI1NiIs[...]24V7wA",
      "loginServer": "contosouniversity.azurecr.io"
    }
    ```
    
    然後你就可以用以下命令登入，請注意，登入帳號是固定的 `00000000-0000-0000-0000-000000000000` 喔，不能亂打！
    
    ```bash
    docker login contosouniversity.azurecr.io --username 00000000-0000-0000-0000-000000000000 --password eyJhbGciOiJSUzI1NiIs[...]24V7wA
    ```
    
    > 注意：不要忘記，這組帳號密碼的有效期間**只有 3 小時**而已！
    
3.  [AD Service principal](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication?WT.mc_id=DT-MVP-4015686#service-principal)
    
    如果你想在 CI/CD 的 Pipelines 存取 ACR 的話，可以先在 Azure AD 建立 [service principal](https://learn.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals?WT.mc_id=AZ-MVP-4015686) 物件，然後透過 [Azure RBAC](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal) 進行 IAM 授權，讓該 **service principal** 可以對 ACR 進行 Pull, Push 或其他管理動作！
    
    常用來授權的角色有：**AcrPull** (pull)、**AcrPush** (pull and push) 與 **Owner** (pull, push, and assign roles to other users)
    
    > 完整的授權角色清單可參考 [Azure Container Registry roles and permissions](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-roles?WT.mc_id=AZ-MVP-4015686)
    
    完整的設定步驟，請參考 [Azure Container Registry authentication with service principals](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-auth-service-principal?WT.mc_id=AZ-MVP-4015686) 文件說明。
    
4.  [Managed identity for Azure resources](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication-managed-identity?WT.mc_id=DT-MVP-4015686)
    
    如果你想從現有的 Azure 服務或資源存取 ACR 的話，例如透過 VM 或其他 PaaS 服務，就可以用這種方式進行驗證，這種認證方法也是適用於 CI/CD pipeline 的情境，不過僅限於從現有 Azure 資源存取 ACR 的情況。
    
5.  [Integrate with AKS](https://learn.microsoft.com/en-us/azure/aks/cluster-container-registry-integration?toc=/azure/container-registry/toc.json&bc=/azure/container-registry/breadcrumb/toc.json&WT.mc_id=AZ-MVP-4015686)
    
    現在 AKS 與 ACR 整合已經是非常簡單了，幾個命令執行完就可以整合完畢！
    
    ```bash
    az aks update -n myAKSCluster -g myResourceGroup --attach-acr <acr-name>
    ```
    
    如果要移除 AKS 與 ACR 的連結，也是一個命令搞定！
    
    ```bash
    az aks update -n myAKSCluster -g myResourceGroup --detach-acr <acr-name>
    ```
    
    除此之外，使用 `az acr import` 命令還能將 Docker Hub 上面的 image 直接複製一份到 ACR 之中，還挺方便的！
    
    ```bash
    az acr import  -n contosouniversity --source docker.io/library/nginx:latest --image nginx:v1
    ```
    
    上述命令會將 `docker.io/library/nginx:latest` 匯入成為 `nginx:v1`，你之後就可以用 `docker pull contosouniversity.azurecr.io/nginx:v1` 下載 image 回來！
    
6.  [Repository-scoped access token](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-repository-scoped-permissions?WT.mc_id=AZ-MVP-4015686)
    
    如果你想限制特定使用者「只能」存取特定 repository (image) 的話，就可以使用這個只有 **Premium** 等級才有個授權方式。
    
    > 關於不同的 ACR 等級 (Tier) 可以參考 [Azure Container Registry service tiers](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-skus?WT.mc_id=AZ-MVP-4015686) 文章說明。
    

### 如何透過 REST API 操作 ACR

昨天在思考著如何利用 Azure Container Registry (ACR) 的 [REST API](https://learn.microsoft.com/en-us/rest/api/containerregistry/?WT.mc_id=AZ-MVP-4015686) 來查詢目前我們公司的 Container Registry 中有哪些 image 可用，但是實作的過程卻是意外的困難。一來是 ACR 官方文件關於 REST API 的說明非常精簡，完全沒有提到授權與認證的部分，導致嘗試了好久才正確理出頭緒。以下我將透過 WSL 進行實作，實作過程你就可以看出要如何正確取得 Bearer Token 並成功取得各種 ACR 裡面的資訊。

由於 [Azure Container Registry REST API reference](https://learn.microsoft.com/en-us/rest/api/containerregistry/?WT.mc_id=AZ-MVP-4015686) 上面列出許多 REST API 可用，且這些 REST API 主要是用 OAuth 2.0 作為授權標準，如果你不瞭解 [Microsoft identity platform and OAuth 2.0 authorization code flow](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow?WT.mc_id=DT-MVP-4015686) 的話，很難可以呼叫成功，建議先看瞭解一下，然後才能參考以下步驟取得「正確」的 Bearer Token 才能成功發出 HTTP 要求！

以下我們以 [Repository - Get List](https://learn.microsoft.com/en-us/rest/api/containerregistry/repository/getlist?WT.mc_id=DT-MVP-4015686) 為例，試圖取得 ACR 中所有的 Repository (images) 清單：

1.  先透過 cURL 發出一次呼叫，取得未授權的結果
    
    ```bash
    export registry="contosouniversity.azurecr.io"
    curl -v https://$registry/acr/v1/_catalog
    ```
    
    這個過程你會取得一段 JSON 結果：
    
    ```json
    {
      "errors": [
        {
          "code": "UNAUTHORIZED",
          "message": "authentication required, visit https://aka.ms/acr/authorization for more information.",
          "detail": [
            {
              "Type": "registry",
              "Name": "catalog",
              "Action": "*"
            }
          ]
        }
      ]
    }
    ```
    
    從回應的 HTTP Headers 中你也會看到以下 `Www-Authenticate` 標頭內容：
    
    ```bash
    Www-Authenticate: Bearer realm="https://contosouniversity.azurecr.io/oauth2/token",service="contosouniversity.azurecr.io",scope="registry:catalog:*"
    ```
    
2.  接著透過 Basic Auth 認證方式取得 OAuth 2.0 提供的 Access Token
    
    這個步驟是最困難的，因為 [Access Tokens - Get](https://learn.microsoft.com/en-us/rest/api/containerregistry/accesstokens/get?WT.mc_id=AZ-MVP-4015686) 文件通通沒有說明！ 😠
    
    首先，你要透過 `/oauth2/token` 取得 Token，但必須額外傳入 `service` 與 `scope` 參數才行！這兩個參數，都來自於上個步驟的 `Www-Authenticate` 標頭值中，且不同的 REST API 呼叫，會有不同的 `scope` 值，而且也沒有文件可以查，反正就先呼叫失敗一次，就知道需要哪些 `scope` 了！
    
    其次就是 Basic Auth 認證的帳號、密碼的來源，文件也都沒有交代，我完全是瞎子摸象，有點痛苦！還好先有看過 [Authenticate with an Azure container registry](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication?WT.mc_id=AZ-MVP-4015686) 這份文件，否則還真的怎樣都猜不到。
    
    ```bash
    export user='00000000-0000-0000-0000-000000000000'
    export pass=$(az acr login --expose-token --name contosouniversity -o json | jq '.accessToken' -r)
    export scope="registry:catalog:*"
    curl -s -u "$user:$pass" "https://$registry/oauth2/token?service=$registry&scope=$scope"
    ```
    
    這個命令可以取得 OAuth 2.0 標準的 `access_token` 與 `refresh_token` 來用：
    
    ```json
    {
      "access_token": "eyJhbGciOiJSUzI1NiIsIn[...]BWoOTane5d6mg",
      "refresh_token": "eyJhbGciOiJSUzI1NiIsIn[...]CSFvfL6Whsueg"
    }
    ```
    
3.  呼叫原本要呼叫的 API 並傳入 Bearer Token
    
    我可以透過 [jq](https://stedolan.github.io/jq/) 快速篩選出我要的 `access_token`
    
    ```bash
    export accessToken=$(curl -s -u "$user:$pass" "https://$registry/oauth2/token?service=$registry&scope=$scope" | jq -C '.access_token' -r)
    ```
    
    接著加入 `Authorization` 標題，並放入上個步驟取得的 `access_token`，即可成功呼叫 REST API 囉！
    
    ```bash
    curl -s -H "Authorization: Bearer $accessToken" https://$registry/acr/v1/_catalog | jq -C
    ```
    
    取得的結果為：
    
    ```json
    {
      "repositories": [
        "nginx"
      ]
    }
    ```
    

### 結語

其實 ACR 功能超多、超好用，可惜 Microsoft Docs 的文件跟不太上，但我後來發現，其實在 [ACR 的 GitHub Repo](https://github.com/Azure/acr/) 都有完整的[文件](https://github.com/Azure/acr/tree/main/docs)，也都有在更新，大家想深入瞭解可以上去查閱！

### 相關連結

-   [Registry authentication options - Azure Container Registry | Microsoft Docs](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication?WT.mc_id=DT-MVP-4015686)
-   [Integrate Azure Container Registry with Azure Kubernetes Service - AKS | Microsoft Docs](https://learn.microsoft.com/en-us/azure/aks/cluster-container-registry-integration?toc=/azure/container-registry/toc.json&bc=/azure/container-registry/breadcrumb/toc.json&WT.mc_id=DT-MVP-4015686)
-   [Azure Container Registry REST API reference](https://learn.microsoft.com/en-us/rest/api/containerregistry/?WT.mc_id=AZ-MVP-4015686)
-   [Create a token with repository-scoped permissions](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-repository-scoped-permissions?WT.mc_id=DT-MVP-4015686) (Preview)
-   [Azure/acr](https://github.com/Azure/acr): Azure Container Registry samples, troubleshooting tips and references
    -   [Azure Container Registry's support of getting Bearer token using **Basic Authentication**](https://github.com/Azure/acr/blob/main/docs/Token-BasicAuth.md)
    -   [Azure Container Registry integration with **Azure Active Directory**](https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md)
-   [Docker Registry HTTP API V2](https://docs.docker.com/registry/spec/api/)
