Interceptando mensagens de erro com o nginx
Tags: webdev, tutorial, nginx, security
Published: 2023-11-12 03:44:20

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](https://www.nginx.com/).

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

![Representação básica da arquitetura de uma app usando nginx](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a98mpyam2i3quf69hdxs.png)

## 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](https://github.com/rAndrade360/nginx-intercept-errs-example).

### 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`.

```go
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:

```nginx
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:

```bash
$ make run
```
Assim que estiver tudo rodando, vamos testar um dos endpoints de erro:

```bash
$ curl -i localhost/not_found
```

O resultado deve ser esse: 

```bash
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:
```nginx
       #...
        location / {
            proxy_intercept_errors on;
            proxy_pass http://app:8000;
        }
      #...
```

Chamando o mesmo endpoint que antes, temos esse novo retorno:
```bash
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:

```bash
$ curl -i localhost/bad_request
```
Que retorna a seguinte resposta:

```bash
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`](https://nginx.org/en/docs/http/ngx_http_core_module.html#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 <a href="https://unsplash.com/@davfts?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">David Pupăză</a> on <a href="https://unsplash.com/photos/text-heNwUmEtZzo?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>