LGPD и искажение конфиденциальных данных в базе данных dev и staging — часть 1


Справочная информация

Общий закон о защите персональных данных (ОЗПД) направлен на регулирование прав и обязанностей при работе с персональными данными. Участвуя в обсуждении этой темы, команда, в которой я работаю, пришла к выводу, что безопаснее и разумнее для баз данных dev и staging не иметь реальных персональных данных клиентов по нескольким причинам:

a) поскольку разработчики работают удаленно, доступ к этим базам данных более открыт.

b) поскольку нет необходимости в том, чтобы среды разработки и постановки имели реальные данные, безопаснее и эффективнее не иметь их, чем создавать стратегии и ресурсы для их защиты.

Задача

В нашей производственной базе данных есть две важные базы данных, содержащие конфиденциальные данные, которые необходимо подделать и сделать доступными в разработке и постановке. Задача состояла в том, чтобы создать программу, которая автоматизирует этот процесс создания базы данных с ложными данными из производственной базы данных; при этом необходимо иметь возможность выбирать, какую исходную базу данных и какую целевую среду использовать.

Проект может быть таким:

  1. Создайте копию производственной базы данных
  2. Вставьте эту копию во временную базу данных
  3. Подделка конфиденциальных данных (часть 2)
  4. Создайте новую копию с поддельными данными
  5. Вставка в банк разработки и/или постановки
  6. Удаление сгенерированных промежуточных файлов (часть 2)

Отказ от ответственности

В целях информационной безопасности базы данных, таблицы и столбцы будут вымышлены. Кроме того, в примерах используются более компактные и простые таблицы, поскольку цель — показать и объяснить разработанный код.

Часть 1

Программа была сделана на языке Golang, и я назвал ее fakedb. В этой первой части рассказывается о том, как fakedb использует ресурсы CLI-клиентов mysql) и mysqldump) для решения пунктов 1, 2, 4 и 5 представленного ранее проекта.

С полным кодексом можно ознакомиться на сайте https://github.com/samuelralmeida/fakedb.

Структура производственной базы данных

Код SQL для создания производственной базы данных с данными для нашего кода находится в файле populate.sql.

Выполните exec, mysql и mysqldump

mysql и mysqldump — это клиенты CLI для работы с базами данных напрямую, например, приведенный ниже код создает базу данных temp прямо из терминала:

mysql -user=root --password=minha_senha -e "CREATE DATABASE temp"
Войдите в полноэкранный режим Выход из полноэкранного режима

Golang предоставляет встроенный пакет os/exec, который позволяет выполнять команды терминала из кода. Приведенный ниже пример делает то же самое, что и предыдущий код:

// ...
import "os/exec"

func dropDatabase(dbName) error {
    cmd := exec.Command("mysql", "--user=root", "--password=minha_senha", "-e", "CREATE DATABASE temp")
    err := cmd.Run()
    return err
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Файл mysqldb содержит функции, которые мы используем для удаления базы данных, если она существует (DropDatabase), создания базы данных (CreateDatabase), экспорта данных (DumpDatabase), импорта данных (RestoreData).

Функция экспорта данных получает имя генерируемого sql-файла. В терминале это будет выглядеть следующим образом:

mysqldump -u root -p client > prod_trem.sql
Войдите в полноэкранный режим Выход из полноэкранного режима

Функция импорта данных принимает имя файла с данными, которые необходимо вставить:

mysql -u root -p temp < prod_trem.sql
Войдите в полноэкранный режим Выход из полноэкранного режима

Эта особенность файла, являющегося входом для клиента mysql, означает, что нам нужно буферизировать файл и управлять stdin в Go, чтобы использовать его вместе с mysql:

func RestoreData(filename string, args credentials.ConnectionParams) error {
    bytes, err := ioutil.ReadFile(fmt.Sprintf("./%s", filename))
    if err != nil {
        return fmt.Errorf("restoreData: error to read file: %w", err)
    }

    dropArgs := []string{
        fmt.Sprintf("--host=%s", args.Host),
        fmt.Sprintf("--port=%s", args.Port),
        fmt.Sprintf("--user=%s", args.User),
        fmt.Sprintf("--password=%s", args.Password),
        args.Database,
    }

    cmd := exec.Command("mysql", dropArgs...)

    stdin, err := cmd.StdinPipe()
    if err != nil {
        return fmt.Errorf("restoreData: error to get stdin pipe: %w", err)
    }

    go func() {
        defer stdin.Close()
        stdin.Write(bytes)
    }()

    _, err = cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("restoreData: error to run command: %w", err)
    }

    return nil
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Гороутины для восстановления данных

Наиболее трудоемкими этапами являются: a) создание дампа базы данных, что, поскольку это одна база данных, мы не можем делать параллельно; и b) восстановление данных в новую базу данных, что мы можем улучшить производительность, распараллелив этот шаг, поскольку восстановление происходит для баз данных dev и staging.

В приведенном ниже коде показано, как мы открываем goroutine для каждой целевой базы данных, самостоятельно контролируем возможные ошибки и ждем их завершения с помощью sync.WaitGroup.

// goroutine para alvos
func restoreTargets(filename string, targets []credentials.ConnectionParams) error {
    var (
        wg   sync.WaitGroup
        errs = []string{}
    )

    for _, target := range targets {
        t := target

        wg.Add(1)
        go func(credential credentials.ConnectionParams) {
            defer wg.Done()

            var err error

            log.Printf("drop database if exixts - %s", credential.Database)
            err = mysqldb.DropDatabase(credential)
            if err != nil {
                err = fmt.Errorf("target - %s: %w", credential.Database, err)
                errs = append(errs, err.Error())
                return
            }

            log.Printf("create database - %s", credential.Database)
            err = mysqldb.CreateDatabase(credential)
            if err != nil {
                err = fmt.Errorf("target - %s: %w", credential.Database, err)
                errs = append(errs, err.Error())
                return
            }

            log.Printf("restore database - %s", credential.Database)
            err = mysqldb.RestoreData(filename, credential)
            if err != nil {
                err = fmt.Errorf("target - %s: %w", credential.Database, err)
                errs = append(errs, err.Error())
                return
            }

            log.Printf("DONE: restore database - %s", credential.Database)

        }(t)
    }

    wg.Wait()

    if len(errs) > 0 {
        return errors.New(strings.Join(errs, "&&"))
    }
    return nil
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы используем это же рассуждение для параллельного запуска сценария для разных банков, одна goroutine для банка поездов и другая для банка вещей, как видно из файла main.go. В наших тестах использование goroutines сэкономило нам 30% времени, затраченного без них.

Доступ к банкам через переменные среды

Чтобы не оставлять данные доступа к банку в виде жесткого кода, мы используем переменные окружения, которые загружаются внутри контейнера docker, запускающего весь скрипт. Переменные находятся в файле .env, а файл example.env содержит скелет этих переменных.

Образ docker, созданный для запуска сценария, основан на образе Mysql Docker, который уже поставляется с установленными клиентами mysql и mysldump. Также, используя переменную окружения MYSQL_ROOT_PASSWORD, уже запускается локальный банк внутри контейнера, который используется как промежуточный банк для получения производственных данных, подделки и экспорта. Поэтому тот же пароль используется в переменной окружения INTERMEDIATE_PASS.

Преимущество использования этого локального банка контейнеров в качестве промежуточного заключается в том, что при завершении сценария и удалении контейнера все промежуточные данные, созданные сценарием, также удаляются.

Подробнее…

В первой части мы рассмотрели манипуляции с банками, вторая часть будет посвящена логике генерирования фальшивых данных для банка. Статья получилась немного большой и плотной из-за деталей темы и кода, но я надеюсь, что она вам помогла. До скорой встречи.

Оцените статью
Procodings.ru
Добавить комментарий