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)