Criar um proxy de API para um GeoCatalog usando o Gerenciamento de API do Azure

Este artigo orienta você na configuração do Azure API Management (APIM) como um proxy de API na frente de um GeoCatalog do Computador Planetário Pro da Microsoft. Com essa configuração, você pode:

  • Habilitar o acesso anônimo: os chamadores não precisam de suas próprias credenciais do Microsoft Entra. O APIM autentica-se no GeoCatalog em seu nome usando uma identidade gerenciada.
  • Autenticação não Entra: os chamadores podem dar suporte a métodos de autenticação não baseados em Entra. O APIM autentica-se no GeoCatalog em seu nome usando uma identidade gerenciada.
  • Imponha o controle de acesso no nível da coleção: restrinja quais coleções do CATÁLOGO de Acesso Espacial (STAC) são visíveis por meio do proxy, embora o GeoCatalogs não dê suporte nativo ao RBAC (controle de acesso baseado em função) no nível da coleção.

O diagrama a seguir ilustra a arquitetura antes e depois de adicionar o proxy APIM:

Antes Cada chamador é autenticado diretamente no GeoCatalog:

caller ──(Entra token)──► GeoCatalog

Depois O APIM fica entre os chamadores e o GeoCatalog, tratando a autenticação e o controle de acesso:

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

Pré-requisitos

Atribuir a identidade gerenciada ao APIM

Antes que o APIM possa se autenticar no GeoCatalog, você precisa associar a identidade gerenciada atribuída pelo usuário à instância do APIM.

  1. No portal do Azure, navegue até a instância do Gerenciamento de API.
  2. Selecione Identidade na barra lateral esquerda.
  3. Selecione a guia Usuário atribuído.
  4. Selecione Adicionar e escolha a identidade gerenciada atribuída pelo usuário que tem a função Leitor de GeoCatalog em seu GeoCatalog.
  5. Selecione Adicionar para confirmar.

Crie a API no APIM

Defina uma nova API no APIM que faz o proxy das solicitações para o back-end do GeoCatalog.

  1. Na instância do APIM, selecione APIs na barra lateral esquerda.

  2. Selecione + Adicionar API>HTTP.

  3. Defina a API com as seguintes configurações:

    Configurações Valor
    Nome de exibição Um nome descritivo (por exemplo, GeoCatalog API)
    URL do serviço Web Seu ponto de extremidade GeoCatalog (por exemplo, https://<name>.<id>.<region>.geocatalog.spatio.azure.com)
    Esquema da URL HTTPS
    Sufixo de URL da API Deixar vazio (caminho raiz)
    Assinatura necessária Não, para acesso anônimo; Sim, para acesso baseado em chave de assinatura
  4. Selecione Criar.

Definir operações de API

Adicione as operações a seguir para corresponder à superfície da API do GeoCatalog. As operações com curinga (/*) encaminham todas as solicitações correspondentes para o back-end. As operações de coleção explícitas permitem aplicar políticas específicas de coleção posteriormente para controle de acesso.

Nome de exibição Método Modelo de URL
GET GET /*
Obter itens de coleção GET /stac/collections/{collection_id}/items
Obter coleção única GET /stac/collections/{collection_id}
Obter sub-recursos da coleção GET /stac/collections/{collection_id}/*
POST POST /*

Para adicionar cada operação:

  1. Selecione a API que você criou.
  2. Selecione + Adicionar operação.
  3. Insira o nome de exibição, o método e o modelo de URL da tabela anterior.
  4. Clique em Salvar.

Configurar a política no nível da API

A política no nível da API manipula a autenticação e a reescrita de URL para toda a API. Essa política adquire um token da identidade gerenciada atribuída pelo usuário e o anexa a cada solicitação encaminhada ao back-end do GeoCatalog.

  1. Selecione a API que você criou e selecione Todas as operações.
  2. Na seção Processamento de Entrada , selecione o <ícone /> (editor de código).
  3. Substitua o conteúdo da política pela seguinte política:
<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>

Substitua os seguintes espaços reservados:

Espaço reservado Valor
<managed-identity-client-id> O ID do cliente da identidade gerenciada atribuída pelo usuário e associada à APIM
<name>.<id>.<region> Os componentes do seu ponto de extremidade do GeoCatalog
<apim-name> O nome da instância do APIM

A tabela a seguir descreve cada elemento de política:

Elemento de política Propósito
authentication-managed-identity Obtém um token para o público https://geocatalog.spatio.azure.com utilizando a identidade gerenciada especificada e o anexa à solicitação de saída.
set-header (excluir Accept-Encoding) Remove o cabeçalho Accept-Encoding de solicitações de entrada. Veja por que remover a codificação de aceitação.
find-and-replace Reescreve a URL do back-end do GeoCatalog nos corpos das respostas para a URL do gateway da APIM. Sem essa reescrita, os links STAC (self, root, parent, e assim por diante) expõem a URL de backend para os chamadores.

Por que tirar Accept-Encoding

Clientes como Python requests e httpx enviam Accept-Encoding: gzip, deflate por padrão. Quando o back-end recebe esse cabeçalho, ele retorna uma resposta compactada. As políticas de saída da APIM, como find-and-replace atuam sobre o corpo bruto da resposta e não conseguem descompactá-lo; portanto, elas simplesmente não fazem nada. A remoção do cabeçalho força o back-end a retornar uma resposta não compactada que as políticas de saída possam processar.

Note

curl e wget não enviam Accept-Encoding por padrão. Isso significa que as políticas de saída parecem funcionar corretamente quando você testa com essas ferramentas. A inconsistência só aparece com clientes que solicitam compactação.

Impor controle de acesso no nível da coleção

Por padrão, um GeoCatalog expõe todas as suas coleções a qualquer chamador autenticado. Para restringir quais coleções são visíveis por meio do APIM, aplique políticas de nível de operação que bloqueiam a ampla descoberta do STAC e imponham uma lista de permissões.

Definir as coleções permitidas

Crie um valor nomeado no APIM para armazenar a lista de IDs de coleção permitidas:

  1. Na instância do APIM, selecione Valores nomeados na barra lateral esquerda.
  2. Selecione + Adicionar.
  3. Defina o Nome como allowed-collections.
  4. Defina o Valor como uma lista separada por vírgulas de IDs de coleção permitidas (por exemplo, sentinel-2-l2a,landsat-8-c2-l2).
  5. Clique em Salvar.

Bloquear a página de destino e a lista de coleções

Bloqueie as rotas que revelam todas as coleções do catálogo. Adicione as seguintes operações e anexe uma política que retorna 404 imediatamente:

Nome de exibição Método Modelo de URL
Raiz de bloco GET /
Coleções de blocos GET /stac/collections

Aplique a seguinte política de nível de operação a ambas as operações:

<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>

O endpoint STAC /stac/search aceita um parâmetro collections - como uma string de consulta no GET ou no corpo JSON em POST. Sem restrições, um chamador pode pesquisar em todas as coleções do catálogo. As políticas a seguir validam que somente coleções do conjunto permitido são solicitadas.

Adicione duas operações:

Nome de exibição Método Modelo de URL
Pesquisa GET GET /stac/search
Pesquisa POST POST /stac/search

GET /stac/search policy

Essa política valida o parâmetro de collections consulta. Cada valor separado por vírgula deve estar no conjunto permitido. As solicitações sem um collections parâmetro são rejeitadas com 403 Forbidden.

Aplique a seguinte política à operação de pesquisa 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

As expressões de política APIM são executadas em um ambiente C# restrito. Use condition='@{...}' (atributo de aspa única) para que as aspas duplas funcionem dentro da expressão. Evite parâmetros de tipo genéricos (por exemplo, GetValueOrDefault<string>) e expressões lambda do LINQ — em vez disso, use conversões explícitas e loops foreach.

POST /stac/search policy

Essa política analisa o corpo JSON e valida a collections matriz. As solicitações sem um collections parâmetro são rejeitadas com 403 Forbidden.

Aplique a seguinte política à operação de pesquisa 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>

Aplicar as coleções permitidas nos pontos de extremidade de coleção

Sem operações explícitas, solicitações como GET /stac/collections/sentinel-2-l2a ou GET /stac/collections/sentinel-2-l2a/items são encaminhadas para o curinga GET /* e chegam ao back-end sem qualquer verificação no nível da coleção. Aplique a política de parâmetro de caminho que valida collection_id em relação a {{allowed-collections}} nas seguintes operações que você criou em Definir operações de API:

Nome de exibição Método Modelo de URL
Obter coleção única GET /stac/collections/{collection_id}
Obter sub-recursos da coleção GET /stac/collections/{collection_id}/*
Obter itens de coleção GET /stac/collections/{collection_id}/items

Aplique a seguinte política a todas as três operações:

<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>

Aplicar as coletas permitidas nas rotas de tokens SAS

A API do GeoCatalog SAS permite que os usuários gerem tokens de armazenamento e assinem HREFs de ativos. Sem restrições, um usuário poderia obter tokens para qualquer coleção. As políticas a seguir garantem que somente coleções permitidas possam ser acessadas.

Adicione as seguintes operações:

Nome de exibição Método Modelo de URL
Obter token SAS GET /sas/token/{collection_id}
Bloquear sinal SAS GET /sas/sign

Aplique a política de bloqueio 404 (igual à do bloqueio de raiz e de coleções) à operação de sinal de bloqueio SAS. Os usuários devem usar /sas/token/{collection_id} para obter tokens SAS no nível da coleção.

Aplique a seguinte política à operação GET SAS token:

<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>

Aplicar as coletas permitidas nas rotas de dados

Os pontos de extremidade /data/mosaic/ oferecem renderização de blocos, recortes de caixas delimitadoras e registro de pesquisa. Dois grupos de políticas são necessários:

  1. Pesquisa no registro - validar a matriz collections no corpo JSON.
  2. Todas as outras rotas de coleta – valide o parâmetro de collectionId path.

Adicione as seguintes operações:

Nome de exibição Método Modelo de URL
Pesquisa no registro POST POST /data/mosaic/register
Coleta de dados GET GET /data/mosaic/collections/{collectionId}/*

Política de POST para /data/mosaic/register

Essa política valida a collections matriz no corpo JSON em relação ao conjunto permitido. As solicitações sem um collections parâmetro são rejeitadas.

<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>

Política GET /data/mosaic/collections/{collectionId}/*

Essa política valida o collectionId parâmetro de caminho em relação ao conjunto permitido. Aplique essa política às duas operações de coleta de dados 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>

Perguntas frequentes

Como atualizar a lista de coleções permitidas?

Edite o valor nomeado allowed-collections na instância do APIM. Nenhuma alteração de política é necessária.

O que acontece se um chamador omitir o parâmetro de coleções?

A solicitação é rejeitada com 403 Forbidden. Os chamadores sempre devem especificar quais coleções desejam pesquisar.