LGPD e falsear dados sensíveis no banco de dados - parte 2
Tags: database, programming, go, security
Published: 2022-06-13 18:09:15

## Contextualizando

A [Lei Geral de Proteção de Dados Pessoais (LGPD)](https://www.gov.br/cidadania/pt-br/acesso-a-informacao/lgpd) visa regulamentar direitos e deveres no tratamento de dados pessoais. Participando de conversas sobre o tema, o time que trabalho chegou a conclusão que é mais seguro e inteligente que os bancos de dados de desenvolvimento e *staging* não tivessem dados pessoais reais dos clientes por alguns motivos:

a) como os desenvolvedores trabalham de forma remota o acesso a esses bancos é mais aberto.

b) uma vez que não é necessário aos ambientes de desenvolvimento e staging que existam dados reais, é mais seguro e eficiente não tê-los do que criar estratégias e recursos para protegê-los.

## O desafio

Nosso banco de produção possui dois *databases* importantes que possuem dados sensíveis para serem falseados e disponibilizados em desenvolvimento e *staging.* O desafio foi gerar um programa que automatize esse processo de gerar um banco com dados falsos a partir do de produção; ainda é necessário que seja possível escolher qual *database* de origem e qual ambiente de destino.

Um rascunho seria:

1. Fazer a cópia do banco de produção (**parte 1**)
2. Inserir essa cópia em um banco temporário (**parte 1**)
3. Falsear os dados sensíveis
4. Fazer uma nova cópia com os dados falsos (**parte 1**)
5. Inserir no bancos de desenvolvimento e/ou staging (**parte 1**)
6. Excluir arquivos intermediários gerados

## Disclaimer

Por motivos de segurança da informação os databases, tabelas e colunas serão inventados. Além disso, os exemplos usam tabelas menores e mais simples, uma vez que o objetivo é mostrar e explicar o código desenvolvido.

## Parte 1

A parte 1 aborda mais a manipulação do banco de dados usando os clientes **mysql** e **mysqldump** pode ser lido aqui: 

## Parte 2

O programa foi feito em Golang e o chamei de **fakedb**. Essa segunda parte aborda como o **fakedb** gera dados falsos e atualiza o banco. Além disso, tabelas em banco podem ser cridas, colunas adicionadas ou removidas, então o código precisa permitir essa dinamicidade também.

O código completo pode ser acessado em [https://github.com/samuelralmeida/fakedb](https://github.com/samuelralmeida/fakedb)

### Organização do código e interfaces do script

Cada *database* do banco possui um diretório. No nosso exemplo: [**coisa**](https://github.com/samuelralmeida/fakedb/tree/main/fakedata/dbs/coisadb) e [**trem**]( https://github.com/samuelralmeida/fakedb/tree/main/fakedata/dbs/tremdb).

Dentro desse diretório cada tabela que precisar falsear dados tem um arquivo próprio, além de um arquivo “pai” com o mesmo nome do *database* que retorna o grupo de scripts por tabela que devem ser executados.

As duas interfaces abaixo definem as regras a serem seguidas.

```go
// arquivo fakedata/interface.go
package fakedata

import "database/sql"

type FakeDataScript interface {
	DB() *sql.DB
	FetchRows() (*sql.Rows, error)
	PrepareStmt(tx *sql.Tx) (*sql.Stmt, error)
	UpdateData(*sql.Rows, *sql.Stmt) error
	TableName() string
}

type IFakeData interface {
	Scripts(db *sql.DB) []FakeDataScript
}
```

Cada script da tabela atende a interface **FakeDataScript** e cada *database* atende a interface **IFakeData**.

Dessa forma, uma alteração em uma tabela existente só precisa ser corrigida no seu respectivo script. Uma nova tabela que surgir, basta escrever os métodos para ela e devolver no array do database.

### A estrutura do script de falsear dados

Como para cada tabela do banco temos os mesmo métodos definidos pela interface, o que o programa faz é os chamar de forma lógica. Vou detalhar o que ocorre no arquivo [fake_data.go](https://github.com/samuelralmeida/fakedb/blob/main/script/fake_data.go)

1. A função `getDBHandle` cria uma conexão com banco a partir das credenciais recebidas.
2. Para cada tabela do *database* passado vai:
    2.1. Buscar todas as entradas da tabela. (método `FetchRows`)
    2.2. Iniciar uma transaction para garantir que todas as alterações sejam feitas por inteiro ou nada seja feito
    2.3. Preparar um *statement* a partir da transaction com o UPDATE da respectiva tabela. (método `PrepareStmt`)
    2.4. Atualizar os dados falsos. (método `UpdateData`)
    2.5. Fazer o *commit* da transaction.

É dentro do `UpdateData` que o principal ocorre:

```go
func (c FakeClientTable) UpdateData(rows *sql.Rows, stmt *sql.Stmt) error {
	for rows.Next() {
		var ID int64
		err := rows.Scan(&ID)
		if err != nil {
			return fmt.Errorf("rows scan error: %w", err)
		}

		name := helper.GenerateName()
		cpf := helper.GenerateCpf()
		address := helper.GenerateAddress()

		_, err = stmt.Exec(name, cpf, address, ID)
		if err != nil {
			return fmt.Errorf("update exec error: %w", err)
		}
	}

	if err := rows.Err(); err != nil {
		return fmt.Errorf("rows error: %w", err)
	}

	return nil
}
```

O código acima é do exemplo para a tabela de cliente. O método recebe as linhas retornadas do banco e o *statement* de update; para cada linha um novo nome, CPF e endereço é gerado e a linha é atualizada usando o `Exec` do *statement*.

Para gerar os dados falsos usamos o pacote [gogakeit](https://github.com/brianvoe/gofakeit). Ele pode ser chamado diretamente como no caso da tabela de [_lead_](https://github.com/samuelralmeida/fakedb/blob/main/fakedata/dbs/coisadb/lead.go#L49): `email := gofakeit.Email()`.

Deixei esse caso de exemplo, mas acredito que o melhor é encapsular dentro do nosso pacote *helper* caso queira ou precise alterar o pacote **gofakeit:**

```go
// fakedata/helper/generate_data.go
package helper

import (
	"fmt"
	"github.com/brianvoe/gofakeit/v6"
)

func GenerateAddress() string {
	types := []string{"Avenida", "Rua", "Travessa"}
	street := fmt.Sprintf("%s %s %s, %d",
		types[gofakeit.Number(0, 2)],
		gofakeit.StreetPrefix(),
		gofakeit.StreetName(),
		gofakeit.Number(0, 3000),
	)
	return street
}
```

### O caso CPF

O **gofakeit** não gera CPFs e é importante que eles sejam válidos para que não quebre validações que existam no back ou frontend. Além disso, CPFs costumam ser chave única no banco, portanto, apesar de ser gerado aleatório não pode ser repetidos. Por isso criamos uma função para gerar cpfs falsos [cpf.go](https://github.com/samuelralmeida/fakedb/blob/main/fakedata/helper/cpf.go) que controla quais já foram gerados usando um *map*.

### CLI, Dockerfile e Makefile

Usamos o pacote **flag** nativo do Go para permitir parâmetros na chamada para definir quais databases de origem dos dados, no nosso exemplo permite **trem**, **coisa** ou **all** (default). E também decidir o destino **dev**, **staging** ou **all** (default):

```go
// main.go
// ...
func main() {
	var source string
	var target string

	flag.StringVar(&source, "source", "all", "Specify which database. Options: all, trem, coisa. Default: all")
	flag.StringVar(&target, "target", "all", "Specify which dump destination. Options: all, dev, staging. Default: all")
	flag.Parse()

	sources := map[string]bool{"all": true, "trem": true, "coisa": true}
	targets := map[string]bool{"all": true, "dev": true, "staging": true}

	if _, ok := sources[source]; !ok {
		log.Fatalf("%s is a invalid source. Options: all, farme, indicame.", source)
	}

	if _, ok := targets[target]; !ok {
		log.Fatalf("%s is a invalid target. Options: all, dev, staging.", target)
	}

//...
}
```

Optamos por usar um container Docker para rodar o script para padronizar o ambiente que é executado e facilita subir a imagem e rodar o container em uma máquina da AWS.

Usamos uma imagem de Go intermediária para compilar o programa e uma imagem definitiva do Mysql que já sobe um banco local automaticamente ao iniciar o container. E copiamos o executável do nosso programa para essa imagem.

```docker
// Dockerfile
FROM golang:1.18 AS build

COPY . /app
WORKDIR /app

RUN go build -o fakedb main.go

FROM mysql:8.0.23

RUN mkdir app
COPY --from=build /app/fakedb /app
WORKDIR /app
```

E para facilitar executar e automatizar o programa preparamos um Makefile que facilita a fazer build da imagem, iniciar o container, acessar e executar o container, etc.

```makefile
build:
	docker build -t dumpfakedb .

run:
	docker run -d --name dumpfakedb --env-file .env dumpfakedb

bash:
	docker exec -it dumpfakedb bash

remove:
	docker container rm dumpfakedb -f

fakedb-all:
	docker exec -it dumpfakedb ./fakedb

fakedb-all-dev:
	docker exec -it dumpfakedb ./fakedb --target=dev

fakedb-trem-staging:
	docker exec -it dumpfakedb ./fakedb --source=trem --target=staging
```

## Fim

Essa foi a segunda parte que abordou mais o falseamento dos dados e execução do programa. O artigo ficou um pouco grande e denso devido aos detalhes do assunto e do código, mas espero que tenha ajudado você. Até breve.