Como Desenvolvedores de Software, o cuidado com o quesito segurança de nossas apps é constante e nunca é demais. Sempre que uma de nossas aplicações se tornam públicas, temos o máximo de cuidado para não expor informações privadas e que possam comprometer o funcionamento da mesma, ou até mesmo do negócio como um todo.
Um ponto crucial de atenção que todos devemos ter ao expor uma aplicação para o público é como ela se comporta quando ocorre algum erro e o que é reportado para o usuário nesses cenários. Resposta de erros mal tratadas podem não ser nada demais para a maioria dos usuários, mas tenha certeza que para alguém, vai ser um prato cheio.
Com o intuito de ajudar a mitigar esses problemas, este artigo aborda o uso da feature proxy_intercept_errors
do nginx.
O que é o nginx
O nginx é um web server largamente utilizado que pode ter muitas utilidades, as mais comuns são Load Balancer e Reverse Proxy. Nesses casos, podemos fazer diversas tratativas nos detalhes das requisições antes e depois do processamento por parte de nossas apps.
A imagem abaixo é uma representação básica de como o nginx pode ser utilizado:
Substituindo mensagens de erro
Para vermos a interceptação de erros em ação, vamos criar um projetinho simples usando Go, nginx e docker-compose.
Você pode encontrar o repositório de exemplo aqui.
Aplicação
Nosso exemplo é uma aplicação simples em Go contendo um http server simples com as seguintes rotas e seus respectivos códigos de retorno:
-
/success
,200
; -
/bad_request
,400
; -
/not_found
,404
; -
/internal_server_error
,500
.
package main import ( "net/http" "github.com/gofiber/fiber/v2" ) func main() { app := fiber.New() app.Get("/success", func(c *fiber.Ctx) error { c.Set("Content-Type", "application/json") return c.SendString(`{"message": "success"}`) }) app.Get("/bad_request", func(c *fiber.Ctx) error { c.Set("Content-Type", "application/json") return c.Status(http.StatusBadRequest).SendString(`{"error": "The new password cannot be the same as the previous one"}`) }) app.Get("/not_found", func(c *fiber.Ctx) error { c.Set("Content-Type", "application/json") return c.Status(http.StatusNotFound).SendString(`{"error":"This resource is not found at database 'vuln'"`) }) app.Get("/internal_server_error", func(c *fiber.Ctx) error { c.Set("Content-Type", "application/json") return c.Status(http.StatusInternalServerError).SendString(`{"error":"Error log with private information"`) }) app.Listen(":8000") }
A ideia é justamente podermos simular vários códigos de erro para testar nossa feat de interceptação de erros. Imagine que em um cenário real qualquer um desses retornos possa carregar uma informação que o cliente não possa ter acesso.
Configuração do nginx
De maneira bem simples, nosso arquivo de configuração do nginx ficou mais ou menos assim:
events{} http { server { listen 80; error_page 404 500 /custom_err.html; error_page 400 @error400; location / { # proxy_intercept_errors on; proxy_pass http://app:8000; } location = /custom_err.html { root /usr/share/nginx/html; internal; } location @error400 { default_type application/json; internal; return 400 '{"error": {"status_code": 400,"status": "Bad Request"}}'; } } }
Sim, a linha comentada é intencional.
Testando a aplicação
Antes de entendermos melhor essa como tudo funciona, vamos ver nosso exemplo em ação. Execute o seguinte comando para iniciar nossa aplicação:
$ make run
Assim que estiver tudo rodando, vamos testar um dos endpoints de erro:
$ curl -i localhost/not_found
O resultado deve ser esse:
HTTP/1.1 404 Not Found Server: nginx/1.25.3 Date: Sun, 12 Nov 2023 02:42:58 GMT Content-Type: application/json Content-Length: 56 Connection: keep-alive {"error":"This resource is not found at database 'vuln'"
E voila! Tivemos uma resposta com informações comprometedoras.
Interceptando erros
Removendo o comentário, nosso location /
fica assim:
#... location / { proxy_intercept_errors on; proxy_pass http://app:8000; } #...
Chamando o mesmo endpoint que antes, temos esse novo retorno:
HTTP/1.1 404 Not Found Server: nginx/1.25.3 Date: Sun, 12 Nov 2023 02:43:50 GMT Content-Type: text/html Content-Length: 250 Connection: keep-alive ETag: "654cd606-fa" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Error page</title> </head> <body> <h1>Ops... This is a custom error page</h1> </body> </html>
Isso significa que o Nginx interceptou nosso retorno de erro e substituiu pelo retorno de nossa página de erro customizada. E segundo a documentação, esse recurso é desabilitado por padrão.
É possível também retornar alguns outros formatos de resposta, como quando executamos o comando:
$ curl -i localhost/bad_request
Que retorna a seguinte resposta:
HTTP/1.1 400 Bad Request Server: nginx/1.25.3 Date: Sun, 12 Nov 2023 02:57:55 GMT Content-Type: application/json Content-Length: 55 Connection: close {"error": {"status_code": 400,"status": "Bad Request"}}
Como já deu para perceber, a resposta a ser retornada é definida pela config error_page
.
Conclusão
Por mais que seja uma configuração simples, a interceptação de erros por parte do nginx pode ajudar a mitigar vários problemas. Esse post demonstrou uma das muitas maneiras de utilizar esse recurso.
Não esqueça de testar você mesmo o nosso exemplo aqui apresentado e explorar as mais diversas formas de usar essa e outras features do nginx.
And keep learning!
Créditos de imagem:
Cover image: Photo by David Pupăză on Unsplash
Top comments (0)