使用 Azure API Management 建立 GeoCatalog 的 API 代理

本文將引導你如何在 Microsoft Planetary Computer Pro GeoCatalog 前設置 Azure API 管理 (APIM)作為 API 代理。 透過這種配置,你可以:

  • 啟用匿名存取:來電者不需要自己的 Microsoft Entra 憑證。 APIM 會代表他們使用管理身份向 GeoCatalog 進行認證。
  • 非 Entra 認證:來電者可支援非 Entra 的認證方法。 APIM 代表他們使用 Managed ID 向 GeoCatalog 進行認證。
  • 強制執行收藏層級存取控制:限制透過代理可見的時空存取目錄(STAC)集合,即使地理目錄原生不支援集合層級的角色基礎存取控制(RBAC)。

下圖展示了加入 APIM 代理前後的架構:

之前 每位來電者都直接向 GeoCatalog 進行認證:

caller ──(Entra token)──► GeoCatalog

之後 APIM 位於呼叫者與 GeoCatalog 之間,負責認證與存取控制:

caller ──(anonymous / APIM Subscription Keys)──► APIM ──(managed identity token)──► GeoCatalog

先決條件

將受管理身份指派給 APIM

在 APIM 能認證你的 GeoCatalog 之前,你需要將使用者指派的管理身份與 APIM 實例關聯。

  1. Azure 入口網站中,流覽至您的 API 管理實例。
  2. 從左側邊欄選擇 「身份 」。
  3. 選取 使用者指派的 索引標籤。
  4. 選取 [新增],然後選擇在 GeoCatalog 上具有 GeoCatalog 讀者角色的使用者指派受控識別。
  5. 選取 [新增] 並確認。

在 APIM 中建立 API

在 APIM 中定義一個新的 API,將請求代理到你的 GeoCatalog 後端。

  1. 在你的 APIM 實例中,從左側邊欄選擇 API

  2. 選擇 + 新增 API>HTTP

  3. 請依以下設定配置 API:

    Setting 價值
    顯示名稱 描述性名稱(例如, GeoCatalog API
    Web 服務 URL 你的 GeoCatalog 端點(例如, https://<name>.<id>.<region>.geocatalog.spatio.azure.com
    URL 協議 HTTPS
    API URL 尾碼 留空(根路徑)
    需訂閱 不,匿名存取是,訂閱金鑰存取
  4. 選取 ,創建

定義 API 操作

新增以下操作以匹配 GeoCatalog API 表面。 萬用卡(/*)操作會將所有匹配的請求轉發到後端。 明確的收集操作讓你能在之後套用特定收集的政策來控制存取。

顯示名稱 方法 網址範本
GET GET /*
取得收藏品 GET /stac/collections/{collection_id}/items
取得單個資料集 GET /stac/collections/{collection_id}
取得集合子資源 GET /stac/collections/{collection_id}/*
郵件 郵件 /*

要新增每個作業:

  1. 選擇你建立的 API。
  2. 選取 [+ 新增作業]
  3. 從前述表格輸入 顯示名稱方法網址範本
  4. 選取 [儲存]。

設定 API 層級政策

API 層級政策負責整個 API 的認證與 URL 重寫。 此策略會從使用者指定的管理身份取得一個令牌,並將其附加到每個轉發到 GeoCatalog 後端伺服器的請求。

  1. 選擇你建立的 API,然後選擇 所有操作
  2. [傳入處理 ] 區段中,選取 </> (程式碼編輯器) 圖示。
  3. 請將政策內容替換為以下政策:
<policies>
    <inbound>
        <base />
        <authentication-managed-identity
            resource="https://geocatalog.spatio.azure.com"
            client-id="<managed-identity-client-id>" />
        <set-header name="Accept-Encoding"
            exists-action="delete" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
        <find-and-replace
            from="https://<name>.<id>.<region>.geocatalog.spatio.azure.com"
            to="https://<apim-name>.azure-api.net" />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

取代下列預留位置:

預留位置 價值
<managed-identity-client-id> 指派給 APIM 之使用者指派受控識別的用戶端識別碼
<name>.<id>.<region> 您的 GeoCatalog 端點元件
<apim-name> 你的 APIM 實例名稱

下表描述每個政策元素:

政策元素 Purpose
authentication-managed-identity 使用指定的受控識別取得 https://geocatalog.spatio.azure.com 對象的權杖,並將其附加至傳出要求。
set-header (刪除 Accept-Encoding 移除 Accept-Encoding 入站請求的標頭。 請參考 了解為何移除 Accept-Encoding
find-and-replace 在回應主體中將 GeoCatalog 後端的 URL 改寫為 APIM 閘道的 URL。 若不重寫,STAC 連結(selfrootparent、等)會將後端 URL 暴露給呼叫者。

為什麼要移除 Accept-Encoding

Python requestshttpx 等用戶端預設會傳送 Accept-Encoding: gzip, deflate。 當後端接收到此標頭時,會回傳壓縮後的回應。 APIM 的輸出策略,例如 find-and-replace,在原始回應體上運作,無法解壓縮,因此不會執行任何操作。 移除標頭會導致後端回傳未壓縮的回應,外發策略可以處理該回應。

Note

curlwget 默認不發送 Accept-Encoding。 這表示當你用這些工具測試時,外發政策似乎能正常運作。 這種不一致只有在客戶端要求壓縮時才會顯現。

強制執行集合層級的存取控制

預設情況下,GeoCatalog 會向任何認證呼叫者公開其所有收藏。 若要限制可透過 APIM 看見的集合,請套用作業層級原則,以封鎖廣泛的 STAC 探索並強制執行允許清單。

定義允許的集合

在 APIM 中建立一個命名值來儲存允許的集合 ID 清單:

  1. 在你的 APIM 實例中,從左側邊欄選擇 命名值
  2. 選取 [+ 新增]。
  3. 名稱 設為 allowed-collections
  4. Value 設定為一個逗號分隔的允許集合 ID 清單(例如 sentinel-2-l2a,landsat-8-c2-l2)。
  5. 選取 [儲存]。

封鎖登陸頁面和收藏列表

封鎖會顯示目錄中每個集合的路由。 新增以下操作並附加一個能立即回傳 404 的政策:

顯示名稱 方法 網址範本
區塊根 GET /
封鎖集合 GET /stac/collections

對兩個操作套用以下操作層級政策:

<policies>
    <inbound>
        <base />
        <return-response>
            <set-status code="404" reason="Not Found" />
        </return-response>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

STAC /stac/search 端點接受 collections 參數——在 GET 時作為查詢字串,或在 POST 的 JSON 主體中。 如果沒有防護機制,呼叫端可能會搜尋目錄中的每個集合。 下列原則會驗證只要求允許集合中的集合。

新增兩個運算:

顯示名稱 方法 網址範本
GET 搜尋 GET /stac/search
POST 搜尋 郵件 /stac/search

GET /stac/search 原則

此策略會驗證查詢參數。collections 每個逗號分隔的值必須在允許的集合內。 沒有collections參數的請求會被拒絕。403 Forbidden

GET 搜尋 操作套用以下政策:

<policies>
    <inbound>
        <base />
        <set-variable name="allowedCsv"
            value="{{allowed-collections}}" />
        <choose>
            <when condition='@{
                var allowed = ((string)context
                    .Variables["allowedCsv"])
                    .Trim().ToLower();
                var raw = context.Request.Url.Query
                    .GetValueOrDefault("collections", "");
                if (string.IsNullOrWhiteSpace(raw)) {
                    return true;
                }
                foreach (var c in raw.ToLower().Split(
                    new [] { "," },
                    StringSplitOptions.RemoveEmptyEntries))
                {
                    if (!c.Trim().Equals(allowed)) {
                        return true;
                    }
                }
                return false;
            }'>
                <return-response>
                    <set-status code="403"
                        reason="Forbidden" />
                    <set-body>
                        Collection not allowed.
                    </set-body>
                </return-response>
            </when>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Note

APIM 政策表達式在受限的 C# 環境中執行。 使用 condition='@{...}' (單引號屬性)讓雙引號能在表達式內運作。 避免使用通用型別參數(例如 GetValueOrDefault<string>)和 LINQ lambda,改用明確的 cast 和 foreach loop。

POST /stac/search 原則

此策略解析 JSON 主體並驗證陣列。collections 沒有collections參數的請求會被拒絕。403 Forbidden

POST 搜尋 操作套用以下政策:

<policies>
    <inbound>
        <base />
        <set-variable name="allowedCsv"
            value="{{allowed-collections}}" />
        <set-variable name="requestBody"
            value="@(context.Request.Body
                .As&lt;string&gt;(
                    preserveContent: true))" />
        <choose>
            <when condition='@{
                var allowed = ((string)context
                    .Variables["allowedCsv"])
                    .Trim().ToLower();
                var body = (string)context
                    .Variables["requestBody"];
                var json = Newtonsoft.Json.Linq
                    .JObject.Parse(body);
                var arr = json["collections"]
                    as Newtonsoft.Json.Linq.JArray;
                if (arr == null || arr.Count == 0) {
                    return true;
                }
                foreach (var token in arr) {
                    if (!token.ToString().Trim()
                        .ToLower().Equals(allowed))
                    {
                        return true;
                    }
                }
                return false;
            }'>
                <return-response>
                    <set-status code="403"
                        reason="Forbidden" />
                    <set-body>
                        Collection not allowed.
                    </set-body>
                </return-response>
            </when>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

在集合端點上強制執行允許的集合

若無明確操作,像是 GET /stac/collections/sentinel-2-l2aGET /stac/collections/sentinel-2-l2a/items 等請求將落入 GET /* 通配符,並抵達後端而不需進行集合層級檢查。 將驗證collection_id{{allowed-collections}}的路徑參數政策套用到你在定義 API 操作中建立的以下操作:

顯示名稱 方法 網址範本
取得單一集合 GET /stac/collections/{collection_id}
取得集合子資源 GET /stac/collections/{collection_id}/*
取得收藏品 GET /stac/collections/{collection_id}/items

請對這三項作業套用以下政策:

<policies>
    <inbound>
        <base />
        <set-variable name="allowedCsv"
            value="{{allowed-collections}}" />
        <choose>
            <when condition='@{
                var allowed = ((string)context
                    .Variables["allowedCsv"])
                    .Trim().ToLower();
                var collectionId = (string)context
                    .Request.MatchedParameters[
                        "collection_id"];
                return !collectionId.Trim()
                    .ToLower().Equals(allowed);
            }'>
                <return-response>
                    <set-status code="403"
                        reason="Forbidden" />
                    <set-body>
                        Collection not allowed.
                    </set-body>
                </return-response>
            </when>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

在 SAS 權杖路由上強制執行允許的集合

GeoCatalog SAS API 讓呼叫者能產生儲存代幣並簽署資產 HREF。 在沒有任何限制的情況下,呼叫者可以為任何收藏取得代幣。 以下政策確保僅能存取允許的資料集。

加入以下運算:

顯示名稱 方法 網址範本
取得 SAS 代幣 GET /sas/token/{collection_id}
封鎖 SAS 簽署 GET /sas/sign

404(與根區塊和集合區塊相同)套用到阻擋 SAS 簽章操作。 呼叫者應改用 /sas/token/{collection_id} 來獲取集合級別的 SAS 權杖。

GET SAS 令牌 操作套用以下政策:

<policies>
    <inbound>
        <base />
        <set-variable name="allowedCsv"
            value="{{allowed-collections}}" />
        <choose>
            <when condition='@{
                var allowed = ((string)context
                    .Variables["allowedCsv"])
                    .Trim().ToLower();
                var collectionId = (string)context
                    .Request.MatchedParameters[
                        "collection_id"];
                return !collectionId.Trim()
                    .ToLower().Equals(allowed);
            }'>
                <return-response>
                    <set-status code="403"
                        reason="Forbidden" />
                    <set-body>
                        Collection not allowed.
                    </set-body>
                </return-response>
            </when>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

在資料路由上強制執行允許的集合

/data/mosaic/ 端點提供圖格轉譯、周框裁切和搜尋註冊。 需要兩個政策小組:

  1. 寄存器搜尋 ——驗證 collections JSON 主體中的陣列。
  2. 其他收集路由,驗證collectionId路徑參數。

加入以下運算:

顯示名稱 方法 網址範本
POST 註冊搜尋 郵件 /data/mosaic/register
GET 資料收集 GET /data/mosaic/collections/{collectionId}/*

POST /data/mosaic/register 原則

此政策會驗證 collections JSON 內容中的陣列是否符合允許的集合。 沒有參數 collections 的請求會被拒絕。

<policies>
    <inbound>
        <base />
        <set-variable name="allowedCsv"
            value="{{allowed-collections}}" />
        <set-variable name="requestBody"
            value="@(context.Request.Body
                .As&lt;string&gt;(
                    preserveContent: true))" />
        <choose>
            <when condition='@{
                var allowed = ((string)context
                    .Variables["allowedCsv"])
                    .Trim().ToLower();
                var body = (string)context
                    .Variables["requestBody"];
                var json = Newtonsoft.Json.Linq
                    .JObject.Parse(body);
                var arr = json["collections"]
                    as Newtonsoft.Json.Linq.JArray;
                if (arr == null || arr.Count == 0) {
                    return true;
                }
                foreach (var token in arr) {
                    if (!token.ToString().Trim()
                        .ToLower().Equals(allowed))
                    {
                        return true;
                    }
                }
                return false;
            }'>
                <return-response>
                    <set-status code="403"
                        reason="Forbidden" />
                    <set-body>
                        Collection not allowed.
                    </set-body>
                </return-response>
            </when>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

GET /data/mosaic/collections/{collectionId}/* 原則

此策略會驗證 collectionId 路徑參數與允許集合的關係。 將此政策套用於 GET 資料收集 操作。

<policies>
    <inbound>
        <base />
        <set-variable name="allowedCsv"
            value="{{allowed-collections}}" />
        <choose>
            <when condition='@{
                var allowed = ((string)context
                    .Variables["allowedCsv"])
                    .Trim().ToLower();
                var collectionId = (string)context
                    .Request.MatchedParameters[
                        "collectionId"];
                return !collectionId.Trim()
                    .ToLower().Equals(allowed);
            }'>
                <return-response>
                    <set-status code="403"
                        reason="Forbidden" />
                    <set-body>
                        Collection not allowed.
                    </set-body>
                </return-response>
            </when>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

常見問題

我該如何更新允許的收藏清單?

編輯 allowed-collections APIM 實例中的命名值。 不需要任何政策變更。

如果呼叫者遺漏了集合參數會發生什麼事?

請求被拒絕,顯示 403 Forbidden。 呼叫者必須始終指定他們想搜尋的集合。