DEV Community

Samuel Rodrigues Almeida e Sousa
Samuel Rodrigues Almeida e Sousa

Posted on

LGPD e falsear dados sensíveis no banco de dados - parte 2

Contextualizando

A Lei Geral de Proteção de Dados Pessoais (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

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

Cada database do banco possui um diretório. No nosso exemplo: coisa e trem.

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.

// 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
}
Enter fullscreen mode Exit fullscreen mode

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

  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:

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
}
Enter fullscreen mode Exit fullscreen mode

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. Ele pode ser chamado diretamente como no caso da tabela de lead: 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:

// 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
}
Enter fullscreen mode Exit fullscreen mode

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

// 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)
    }

//...
}
Enter fullscreen mode Exit fullscreen mode

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.

// 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
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)