BLOG | NGINX

Implementando o NGINX como um API Gateway, Parte 1

NGINX-Parte-de-F5-horiz-preto-tipo-RGB
Miniatura de Liam Crilly
Liam Crilly
Publicado em 20 de janeiro de 2021

Esta é a primeira postagem de blog em nossa série sobre a implantação do NGINX Open Source e do NGINX Plus como um gateway de API:

  • Esta postagem fornece instruções detalhadas de configuração para vários casos de uso. Publicado originalmente em 2018, ele foi atualizado para refletir as melhores práticas atuais para configuração de API, usando blocos de localização aninhados para rotear solicitações, em vez de reescrever regras.
  • A Parte 2 amplia esses casos de uso e analisa uma série de salvaguardas que podem ser aplicadas para proteger e assegurar serviços de API de backend em produção.
  • A Parte 3 explica como implantar o NGINX Open Source e o NGINX Plus como um gateway de API para serviços gRPC.

Observação : Exceto quando indicado, todas as informações nesta postagem se aplicam ao NGINX Open Source e ao NGINX Plus. Para facilitar a leitura, o restante do blog se refere simplesmente a “NGINX”.

No centro das arquiteturas de aplicativos modernos está a API HTTP. O HTTP permite que os aplicativos sejam criados rapidamente e mantidos facilmente. A API HTTP fornece uma interface comum, independentemente da escala do aplicativo, desde um microsserviço de propósito único até um monólito abrangente. Ao usar HTTP, os avanços na entrega de aplicativos da web que oferecem suporte a propriedades de Internet em hiperescala também podem ser usados para fornecer entrega de API confiável e de alto desempenho.

Para uma excelente introdução à importância dos gateways de API para aplicativos de microsserviços, consulte Construindo microsserviços: Usando um API Gateway em nosso blog.

Como proxy reverso e balanceador de carga leve e de alto desempenho, o NGINX tem os recursos avançados de processamento HTTP necessários para lidar com o tráfego de API. Isso faz do NGINX a plataforma ideal para construir um gateway de API. Nesta postagem do blog, descrevemos uma série de casos de uso comuns de gateway de API e mostramos como configurar o NGINX para lidar com eles de uma forma eficiente, escalável e fácil de manter. Descrevemos uma configuração completa, que pode formar a base de uma implantação de produção.

Apresentando a API do Warehouse

A função principal do gateway de API é fornecer um ponto de entrada único e consistente para diversas APIs, independentemente de como elas são implementadas ou implantadas no backend. Nem todas as APIs são aplicativos de microsserviços. Nosso gateway de API precisa gerenciar APIs, monólitos e aplicativos existentes que estão passando por uma transição parcial para microsserviços.

Nesta postagem do blog, nos referimos a uma API hipotética para gerenciamento de estoque, a “Warehouse API”. Usamos código de configuração de exemplo para ilustrar diferentes casos de uso. A Warehouse API é uma API RESTful que consome solicitações JSON e produz respostas JSON. O uso de JSON não é, no entanto, uma limitação ou requisito do NGINX quando implantado como um gateway de API; o NGINX é independente do estilo arquitetônico e dos formatos de dados usados pelas próprias APIs.

A Warehouse API é implementada como uma coleção de microsserviços discretos e publicada como uma única API. Os recursos de inventário e precificação são implementados como serviços separados e implantados em diferentes backends. Então a estrutura do caminho da API é:    

api
└── armazém
├── inventário
└── preços

Por exemplo, para consultar o inventário atual do depósito, um aplicativo cliente faz uma solicitação HTTP GET para /api/warehouse/inventory .

Arquitetura de gateway de API para vários aplicativos

Organizando a configuração do NGINX

Uma vantagem de usar o NGINX como um gateway de API é que ele pode desempenhar essa função e, ao mesmo tempo, atuar como um proxy reverso, balanceador de carga e servidor web para o tráfego HTTP existente. Se o NGINX já fizer parte da sua pilha de entrega de aplicativos, geralmente não será necessário implantar um gateway de API separado. No entanto, alguns dos comportamentos padrão esperados de um gateway de API diferem daqueles esperados para tráfego baseado em navegador. Por esse motivo, separamos a configuração do gateway de API de qualquer configuração existente (ou futura) para tráfego baseado em navegador.

Para conseguir essa separação, criamos um layout de configuração que oferece suporte a uma instância NGINX multifuncional e fornece uma estrutura conveniente para automatizar a implantação de configuração por meio de pipelines de CI/CD. A estrutura de diretório resultante em /etc/nginx se parece com isso.

etc/
└── nginx/
├── api_conf.d/ ………………………………… Subdiretório para configuração por API
│ └── warehouse_api.conf …… Definição e política da API do Warehouse
├── api_backends.conf ………………… Os serviços de backend (upstreams)
├── api_gateway.conf …………………… Configuração de nível superior para o servidor de gateway de API
├── api_json_errors.conf ………… Respostas de erro HTTP no formato JSON
├── conf.d/
│ ├── ...
│ └── existing_apps.conf
└── nginx.conf

Os diretórios e nomes de arquivos para todas as configurações de gateway de API são prefixados com api_ . Cada um desses arquivos e diretórios habilita um recurso ou capacidade diferente do gateway de API, conforme explicado em detalhes abaixo. O arquivo warehouse_api.conf é um substituto genérico para os arquivos de configuração discutidos abaixo que definem a API do Warehouse de diferentes maneiras.

Definindo o Gateway de API de Nível Superior

Toda a configuração do NGINX começa com o arquivo de configuração principal, nginx.conf . Para ler a configuração do gateway da API, adicionamos uma diretiva include no bloco http em nginx.conf que faz referência ao arquivo que contém a configuração do gateway, api_gateway.conf (linha 28 logo abaixo). Observe que o arquivo nginx.conf padrão usa uma diretiva include para extrair a configuração HTTP baseada em navegador do subdiretório conf.d (linha 29). Esta postagem do blog faz uso extensivo da diretiva include para ajudar na legibilidade e permitir a automação de algumas partes da configuração.

 

O arquivo api_gateway.conf define o servidor virtual que expõe o NGINX como um gateway de API para clientes. Esta configuração expõe todas as APIs publicadas pelo gateway de API em um único ponto de entrada, https://api.example.com/ (linha 9), protegido por TLS conforme configurado nas linhas 12 a 17. Observe que esta configuração é puramente HTTPS – não há um ouvinte HTTP de texto simples. Esperamos que os clientes da API saibam o ponto de entrada correto e façam conexões HTTPS por padrão.

Esta configuração deve ser estática – os detalhes de APIs individuais e seus serviços de backend são especificados nos arquivos referenciados pela diretiva include na linha 20. As linhas 23 a 26 tratam do tratamento de erros e são discutidas em Respondendo a erros abaixo.

 

Serviço Único vs. Backends de API de microsserviços

Algumas APIs podem ser implementadas em um único backend, embora normalmente esperemos que haja mais de um, por motivos de resiliência ou balanceamento de carga. Com APIs de microsserviços, definimos backends individuais para cada serviço; juntos, eles funcionam como a API completa. Aqui, nossa API Warehouse é implantada como dois serviços separados, cada um com múltiplos backends.

 

Todos os serviços de API de backend, para todas as APIs publicadas pelo API gateway, são definidos em api_backends.conf . Aqui usamos vários pares de endereço IP-porta em cada bloco upstream para indicar onde o código da API é implantado, mas nomes de host também podem ser usados. Os assinantes do NGINX Plus também podem aproveitar o balanceamento de carga de DNS dinâmico para ter novos backends adicionados à configuração de tempo de execução automaticamente.

Definindo a API do Warehouse

A API do Warehouse é definida por vários blocos de localização em uma configuração aninhada, conforme ilustrado pelo exemplo a seguir. O bloco de localização externa ( /api/warehouse ) identifica o caminho base, sob o qual os locais aninhados especificam os URIs válidos que são roteados para os serviços de API de backend. Usar um bloco externo nos permite definir políticas comuns que se aplicam a toda a API (neste exemplo, a configuração de registro na linha 6).

 

O NGINX tem um sistema altamente eficiente e flexível para corresponder o URI da solicitação a uma seção da configuração. A ordem das diretivas de localização não é importante – a correspondência mais específica é escolhida. Aqui, os locais aninhados nas linhas 10 e 14 definem dois URIs que são mais específicos do que o bloco de localização externa; a diretiva proxy_pass em cada bloco aninhado roteia solicitações para o grupo upstream apropriado. A configuração da política é herdada do local externo, a menos que haja necessidade de fornecer uma política mais específica para determinados URIs.

Quaisquer URIs que não correspondam a um dos locais aninhados são manipulados pelo local externo, que inclui uma diretiva catch-all (linha 18) que retorna a resposta 404(Não encontrado) para todos os URIs inválidos.

Escolhendo Amplo vs. Definição precisa para APIs

Existem duas abordagens para a definição de API: ampla e precisa. A abordagem mais adequada para cada API depende dos requisitos de segurança da API e se é desejável que os serviços de backend manipulem URIs inválidos.

No warehouse_api_simple.conf acima, usamos a abordagem ampla para a API do Warehouse, definindo prefixos de URI nas linhas 10 e 14 de modo que um URI que começa com um dos prefixos seja proxy para o serviço de backend apropriado. Com essa ampla correspondência de localização baseada em prefixo, todas as solicitações de API para os seguintes URIs são válidas:

/api/armazém/inventário
/api/armazém/inventário/
/api/armazém/inventário/foo
/api/armazém/inventáriofoo
/api/armazém/inventáriofoo/bar/

Se a única consideração for enviar cada solicitação para o serviço de backend correto, a abordagem ampla fornece o processamento mais rápido e a configuração mais compacta. Por outro lado, uma abordagem mais precisa permite que o gateway da API entenda o espaço URI completo da API, definindo explicitamente o caminho do URI para cada recurso de API disponível. Adotando uma abordagem precisa, a seguinte configuração para roteamento de URI na API do Warehouse usa uma combinação de correspondência exata ( = ) e expressões regulares ( ~ ) para definir todo e qualquer URI válido.

 

Esta configuração é mais detalhada, mas descreve com mais precisão os recursos implementados pelos serviços de backend. Isso tem a vantagem de proteger os serviços de backend de solicitações de clientes malformadas, ao custo de uma pequena sobrecarga adicional para correspondência de expressões regulares. Com essa configuração em vigor, o NGINX aceita alguns URIs e rejeita outros como inválidos:

URIs válidos   URIs inválidos
/api/armazém/inventário   /api/armazém/inventário/
/api/armazém/inventário/prateleira/foo   /api/armazém/inventáriofoo
/api/armazém/inventário/prateleira/foo/caixa/barra   /api/armazém/inventário/prateleira
/api/armazém/inventário/prateleira/-/caixa/-   /api/armazém/inventário/prateleira/foo/bar
/api/armazém/preço/baz   /api/armazém/preços
    /api/armazém/preços/baz/pub

Usar uma definição de API precisa permite que os formatos de documentação de API existentes orientem a configuração do gateway de API. É possível automatizar as definições da API NGINX a partir da especificação OpenAPI (anteriormente chamada de Swagger). Um exemplo de script para essa finalidade é fornecido entre os Gists para esta postagem do blog.

Reescrevendo solicitações de clientes para lidar com mudanças drásticas

À medida que as APIs evoluem, às vezes é necessário fazer alterações que quebram a compatibilidade com versões anteriores e exigem que os clientes sejam atualizados. Um exemplo disso é quando um recurso de API é renomeado ou movido. Ao contrário de um navegador da web, um gateway de API não pode enviar aos seus clientes um redirecionamento (código301 (Movido Permanentemente) ) nomeando o novo local. Felizmente, quando não é prático modificar clientes de API, podemos reescrever solicitações de clientes imediatamente.

No exemplo a seguir, usamos a mesma abordagem ampla do warehouse_api_simple.conf acima, mas neste caso a configuração está substituindo uma versão anterior da API do Warehouse, onde o serviço de preços foi implementado como parte do serviço de inventário. A diretiva de reescrita na linha 3 converte solicitações ao antigo recurso de precificação em solicitações ao novo serviço de precificação.

 

Respondendo a erros

Uma das principais diferenças entre APIs HTTP e tráfego baseado em navegador é como os erros são comunicados ao cliente. Quando o NGINX é implantado como um gateway de API, nós o configuramos para retornar erros da maneira mais adequada aos clientes da API.

A configuração do gateway de API de nível superior inclui uma seção que define como lidar com respostas de erro.

 

O página_de_erro a diretiva na linha 23 especifica que quando uma solicitação não corresponde a nenhuma das definições da API, o NGINX retorna o 400 (Ruim Solicitar) erro em vez do padrão 404 (Não Encontrado) erro. Esse comportamento (opcional) exige que os clientes da API façam solicitações apenas aos URIs válidos incluídos na documentação da API e impede que clientes não autorizados descubram a estrutura do URI das APIs publicadas por meio do gateway da API.

A linha 24 se refere a erros gerados pelos próprios serviços de backend . Exceções não tratadas podem conter rastreamentos de pilha ou outros dados confidenciais que não queremos que sejam enviados ao cliente. Essa configuração adiciona um nível adicional de proteção enviando uma resposta de erro padronizada ao cliente.

A lista completa de respostas de erro padronizadas é definida em um arquivo de configuração separado, referenciado pela diretiva include na linha 25, cujas primeiras linhas são mostradas abaixo. Este arquivo pode ser modificado se um formato de erro diferente de JSON for preferido, com o valor default_type na linha 26 de api_gateway.conf alterado para corresponder. Você também pode ter uma diretiva include separada na seção de política de cada API para referenciar um arquivo diferente de respostas de erro que substituem as respostas globais.

Com essa configuração em vigor, uma solicitação de cliente para um URI inválido recebe a seguinte resposta. [terminal]$ curl -i https://api.example.com/foo HTTP/1.1 400 Bad Request Server: nginx/1.19.5 Content-Type: application/json Content-Length: 39 Conexão: keep-alive {"status":400,"message":"Solicitação incorreta"}[/terminal]

Implementando Autenticação

É incomum publicar APIs sem alguma forma de autenticação para protegê-las. O NGINX oferece diversas abordagens para proteger APIs e autenticar clientes de API. Para obter informações sobre abordagens que também se aplicam a solicitações HTTP regulares, consulte a documentação para listas de controle de acesso (ACLs) baseadas em endereço IP , autenticação de certificado digital e autenticação HTTP Basic . Aqui, nos concentramos em métodos de autenticação específicos de API.

Autenticação de chave API

Chaves de API são um segredo compartilhado conhecido pelo cliente e pelo gateway de API. Uma chave de API é essencialmente uma senha longa e complexa emitida para o cliente de API como uma credencial de longo prazo. Criar chaves de API é simples: basta codificar um número aleatório como neste exemplo.

$ openssl rand -base64 18 7B5zIqmRGXmrJTFmKa99vcit

Na linha 2 do arquivo de configuração do gateway de API de nível superior, api_gateway.conf , incluímos um arquivo chamado api_keys.conf , que contém uma chave de API para cada cliente de API, identificado pelo nome do cliente ou outra descrição. Aqui está o conteúdo desse arquivo:

As chaves de API são definidas dentro de um bloco de mapa . A diretiva map recebe dois parâmetros. O primeiro define onde encontrar a chave da API, neste caso no cabeçalho HTTP apikey da solicitação do cliente, conforme capturado na variável $http_apikey . O segundo parâmetro cria uma nova variável ( $api_client_name ) e a define como o valor do segundo parâmetro na linha onde o primeiro parâmetro corresponde à chave.

Por exemplo, quando um cliente apresenta a chave de API 7B5zIqmRGXmrJTFmKa99vcit , a variável $api_client_name é definida como client_one . Esta variável pode ser usada para verificar clientes autenticados e incluída em entradas de log para auditoria mais detalhada. O formato do bloco de mapa é simples e fácil de integrar em fluxos de trabalho de automação que geram o arquivo api_keys.conf a partir de um armazenamento de credenciais existente.

Aqui, habilitamos a autenticação de chave de API alterando a configuração “ampla” ( warehouse_api_simple.conf ) para incluir uma diretiva auth_request na seção de política que delega a decisão de autenticação a um local especificado.

 

Com a diretiva auth_request (linha 7) podemos, por exemplo, ter a autenticação manipulada por um servidor de autenticação externo, como a introspecção de token OAuth 2.0 . Neste exemplo, adicionamos a lógica para validar chaves de API ao arquivo de configuração do gateway de API de nível superior , no formato do seguinte bloco de localização chamado /_validate_apikey .

 

A diretiva interna na linha 30 significa que este local não pode ser acessado diretamente por clientes externos (somente por auth_request ). Espera-se que os clientes apresentem sua chave de API no cabeçalho HTTP apikey . Se este cabeçalho estiver faltando ou vazio (linha 32), enviamos um401 Resposta (não autorizada) para informar ao cliente que a autenticação é necessária. A linha 35 trata do caso em que a chave da API não corresponde a nenhuma das chaves no bloco do mapa – nesse caso, o parâmetro padrão na linha 2 de api_keys.conf define $api_client_name como uma string vazia – e enviamos um403 Resposta (proibida) para informar ao cliente que a autenticação falhou. Se nenhuma dessas condições corresponder, a chave da API será válida e o local retornará um 204Resposta (Sem conteúdo) .

Com essa configuração em vigor, a API do Warehouse agora implementa a autenticação de chave de API.

$ curl https://api.example.com/api/warehouse/pricing/item001 {"status":401,"message":"Não autorizado"} $ curl -H "apikey: thisIsInvalid" https://api.example.com/api/warehouse/pricing/item001 {"status":403,"message":"Proibido"} $ curl -H "apikey: 7B5zIqmRGXmrJTFmKa99vcit" https://api.example.com/api/warehouse/pricing/item001 {"sku":"item001","price":179.99}

Autenticação JWT

JSON Web Tokens (JWTs) são cada vez mais usados para autenticação de API. O suporte nativo a JWT é exclusivo do NGINX Plus, permitindo a validação de JWTs conforme descrito em Autenticação de clientes de API com JWT e NGINX Plus em nosso blog. Para uma implementação de exemplo, consulte Controlando o acesso a métodos específicos na Parte 2.

Resumo

Este primeiro blog de uma série detalha uma solução completa para implantar o NGINX Open Source e o NGINX Plus como um gateway de API. O conjunto completo de arquivos discutidos neste blog pode ser revisado e baixado do nosso repositório GitHub Gist .

Confira os outros posts desta série:

  • A Parte 2 explora casos de uso mais avançados para proteger serviços de backend de clientes maliciosos ou malcomportados.
  • A Parte 3 explica como implantar o NGINX como um gateway de API para serviços gRPC.

Para experimentar o NGINX Plus, comece hoje mesmo seu teste gratuito de 30 dias ou entre em contato conosco para discutir seus casos de uso.


"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."