Вступление

Cтатья будет полезна разработчикам и инженерам по ИБ, желающим повысить уровень безопасности приложений за счет внедрения проверок, запрещающих вносить секреты в открытом виде в исходный код.

Автор:

Голяков Сергей

DevSecOps, в информационной безопасности с 2013 года, в СПАО «‎Ингосстрах»‎ встраиваю безопасность в процесс разработки

Проблематика

Разработчики размещают в исходном коде различные секреты в открытом виде:

Пример неуспешной проверки запроса на вытягивание
Пример неуспешной проверки запроса на вытягивание

В случае утечки исходного кода либо ознакомления с ним неуполномоченными лицами компания понесет ущерб.

Предлагаемое решение

В статье поэтапно рассмотрен процесс внедрения Gitleaks для анализа pull request на наличие секретов в Azure DevOps Server.

Этапы:

  1. Создать YML-файл конвейера-шаблона, реализующий логику поиска секретов;

  2. Создать валидационную сборку на основе YML-файла;

  3. Настроить запуск валидационной сборки в каждом запросе на вытягивание;

  4. Разработать FAQ для разработчиков и безопасников.

Поиск секретов

Киберканарейка по имени Фикс еще не знает как все сложно, но уже шагнул на тропу
Киберканарейка по имени Фикс еще не знает как все сложно, но уже шагнул на тропу

Секрет — переменная, являющаяся аутентификатором (пароль, JSON Web Token, API-ключ, закрытый криптографический ключ). Допустимо относить к секретам и сами логины, например, опасно, если логин от внешней общедоступной системы, связанной с денежными переводами (например, логин мерчанта) будет известен каждому, кто имеет доступ к исходному коду приложения. А доступ, бывает, имеют не только разработчики, но и бизнес-пользователи.

Поиск секретов — часть процесса по управлению секретами, заключающаяся в выявлении секретов в исходном коде на этапе внесения секретов в исходный код. Иные рекомендации OWASP по секретам: OWASP Secrets Management Cheat Sheet.

Идентификация слабости ПО при хранении секретов в исходном коде: CWE-259: Использование жестко закодированного пароля.

Идентификация риска информационной безопасности: риск OWASP TOP10:05 Security Misconfiguration. Среди рисков, актуальных для Kubernetes, проблемы с секретами даже выделены в целую категорию рисков Top 10 Kubernetes Risks K08: Secrets Management.

Как видим, согласно лучшим практикам секреты искать нужно, не давать вносить секреты в открытом виде в исходный код также необходимо.

Искать секреты можно в:

  1. IDE (привет, shift-left);

  2. Git (Azure Repos в рамках данной статьи).

Искать секреты можно:

  1. Руками (глазами);

  2. Автоматически/автоматизированно.

Стоит отметить, какое бы крутое решение по автоматизированному поиску секретов вы не выбрали, всё равно многие секреты вы будете находить глазами, просто листая код в репозитории. Причин несколько:

  1. Какие-то решения не поддерживают поиск секретов в нескольких строках;

  2. Регулярные выражения от автора выбранного решения могут не поддерживать поиск секретов в:

    1. XML, см. Secrets in web.config files · Issue #813 · gitleaks/gitleaks (github.com);

    2. Строках некоторых языков программирования;

    3. Строках с секретами на русском языке, например такие:
      # мой суперсекретныйпароль = &NB^IRU%&G (движок регулярного выражения не поддерживает кириллицу).

Полезно знать, в каких файлах чаще всего разработчик оставляет секреты применительно в вашей компании.

Чем будем искать секреты? В этой статье рассмотрим внедрение Gitleaks применительно к Azure DevOps Server в процесс анализа запросов на вытягивание при слиянии ветвей. Система управления исходным кодом Azure Repos является частью Azure DevOps Server.

Какой процесс выбрали мы

На момент публикации статьи упрощенно наша схема выглядит так:

Процесс анализа PR на наличие секретов
Процесс анализа PR на наличие секретов

Эта же схема, но текстом и кратко:

  1. Разработчик создает запрос на вытягивание с целью объединить свою feature-ветвь с защищаемой ветвью.

  2. Запускается валидационная сборка с целью проверки секретов во вносимом коде и проверка завершается:

    1. С ошибкой при нахождении секретов;

    2. Успешно с предупреждением при нахождении секрета в репозитории, который внесен в исключение;

    3. Успешно при отсутствии секретов.

Структура проекта

Графически схема работы в разрезе сущностей Azure DevOps Server выглядит так:

Предварительно на агент сборки необходимо положить gitleaks.exe, но можно его каждый раз из репозитория или менеджера пакетов подгружать. Второй вариант приоритетнее.

Реализация процесса

Этапы процесса

Для реализации проверки секретов во вносимом коде потребуется:

  1. Создать YML-файл конвейера-шаблона (pipeline.yml), реализующий логику поиска секретов;

  2. Добавить в защищаемые репозитории другой YML-файл (gitleaks-pipeline.yml) вызывающий YML-файл конвейер-шаблон (pipeline.yml) в качестве шаблона (template);

  3. Создать конвейер на основе YML-файла (создать валидационный конвейер), руководствуясь Create your first pipeline | Microsoft Learn;

  4. Настроить в политике защиты выбранных ветвей (например, текущей и релизной) запуск валидационного конвейера, руководствуясь Git branch policies and settings | Microsoft Learn;

  5. Инструкция на wiki с порядком действия разработчика в случае выявления секретов;

  6. Инструкция для безопасника по обработке ошибочных сработок.

Далее рассмотрим этапы подробнее.

1. Создание YML-файла

Для начала создадим отдельный репозиторий, например, с названием Gitleaks-PR.

Поместим YML-файл pipeline.yml в репозиторий, файл будет шаблоном задач в валидационной сборке — набор задач по поиску секретов в исходном коде.

Предварительные этапы

Пример YML (pipeline.yml):

pipeline.yml - основной блок
# pipeline.yml - основной блок
jobs:
  - job: Gitleaks
    pool: '<имя пула агентов сборки на которых запускать задания>'
    variables:
    # Имя группы секретных переменных с необходимым авторизационным
    # токеном Azure DevOps
    - group: GitleaksVariables
    displayName: "Gitleaks: Поиск секретов"
 
    steps:
 
    # 1. Клонируем репозиторий со скриптами валидационной сборки
    - checkout: git://<проект со скриптами>/_git/Gitleaks-PR@master
    # 2. Клонируем смерженный репозиторий, в котором нужно найти секреты
    - checkout: self

Предварительно потребуется создать группу переменных с названием GitleaksVariables с персональным токеном, используя который бот будет работать с API Azure DevOps Server.

Первый этап: поиск секретов

Запускаем Gitleaks, получает отчет с секретами и сохраняем отчет в файл. Данную работу выполняет Gitleaks.ps1:

pipeline.yml - step запуска Gitleaks
#pipeline.yml - step запуска Gitleaks
# 3. Запуск Gitleaks для поиска секретов в System.DefaultWorkingDirectory
- task: PowerShell@2
  displayName: "Поиск секретов"
  inputs:
    targetType: filePath
    # Запускаемый скрипт, который запускает Gitleaks и складывает отчет
    # в JSON файл внутри $(System.DefaultWorkingDirectory)
    filePath: $(System.DefaultWorkingDirectory)/Gitleaks-PR/Gitleaks.ps1
    arguments: >
      -reportPath $(System.DefaultWorkingDirectory)
    #failOnStderr: true
    # Использовать Power Shell Core
    pwsh: true
    # Отображать ошибки в логе в формате Azure DevOps
    showWarnings: true
  #continueOnError: true

Вот так выглядит лог данного шага в валидационной сборке:

Поиск секретов
Поиск секретов

Второй этап: сохранение отчета

Сохраненный отчет публикуется в результаты конвейера для истории:

pipeline.yml - step публикация отчета
# pipeline.yml - step публикация отчета
# 4. Публикация файлов с отчетами
- task: PublishBuildArtifacts@1
  displayName: "Публикация отчета в конвейер"
  inputs:
    pathToPublish: $(System.DefaultWorkingDirectory)/Gitleaks-report.json
    artifactName: Gitleaks-reports

Публикация отчета Gitleaks в артефакты валидационной сборки
Публикация отчета Gitleaks в артефакты валидационной сборки

Ознакомиться с полным отчетом можно в артефактах валидационной сборки:

Место хранения отчета в валидационной сборке
Место хранения отчета в валидационной сборке
Артефакты валидационной сборки
Артефакты валидационной сборки

Третий этап: принятие решения

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

На данном этапе можно не только принять решение, но и:

  1. Оставить комментарий к запросу на вытягивание с полезной информацией;

  2. Передать во внешнюю систему информацию о найденной уязвимости, например, сгенерировать фичу на устранение уязвимости или отправить данные в SIEM;

  3. Уведомить ответственных за репозиторий по почте или в мессенджере о сработке;

  4. Автоматически проанализировать сработку и принять решение основываясь не на простом факте наличия сработки (возможно ошибочной), но и автоматизировано её обработать;

  5. Запустить иной конвейер, например, выполняющий незамедлительное удаление секрета из ветви, в которой выявлен секрет.

Пример такого этапа:

pipeline.yml - step анализ отчета
# pipeline.yml - step анализ отчета
# 5. Запуск анализатора отчета Gitleak
- task: PowerShell@2
  displayName: "Принятие решения"
  env:
    # Имя переменной из группы секретных переменных "GitleaksVariables"
    TFS_TOKEN: $(tfs_token)
  inputs:
    targetType: filePath
    # Запускаемый скрипт, который принимает решение
    filePath: $(System.DefaultWorkingDirectory)/Gitleaks-PR/Gitleaks-failer.ps1
    # Передаем путь до папки, в которой лежит отчет Gitleaks для принятия решения, TFS_TOKEN это имя переменной с PAT из группы переменных GitleaksVariables
    arguments: >
      -reportPath $(System.DefaultWorkingDirectory) -tfsToken $env:TFS_TOKEN
    # Фейлим сборку если Gitleaks-failer.ps1 инициирует падение
    #failOnStderr: true
    # Использовать Power Shell Core
    pwsh: true
    showWarnings: true
 
- task: PostBuildCleanup@3
  displayName: Очистка папок
  continueOnError: true

Вот так выглядит лог данного шага в валидационной сборке:

Принятие решения на основе содержимого отчета Gitleaks
Принятие решения на основе содержимого отчета Gitleaks

Как видим, разработчику приводится общая информация о том, что ему делать дальше, также разработчик может развернуть список с найденными секретами в маскированном виде:

Список секретов в маскированном виде
Список секретов в маскированном виде

Скрипты автоматизации

Скрипт запуска Gitleaks

По сути, всё что требуется, это запустить Gitleaks и сохранить отчет Gitleaks в папку. Скрипт, выполняющий запуск Gitleaks, может быть таким:

Gitleaks.ps1
param(
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    $reportPath = "" # -report-path - обязательный параметр это папка, куда складывать отчет сканирования
)
 
if ($reportPath -eq "") # проверка существования папки с конфигами
{
    Write-Host "##[error]Папка -reportPath с исходным кодом не задана: '${reportPath}' <- тут должно было быть ее значение/"
    exit 1
}
 
# Инициализация переменных
[string] $Gitleaks_EXE_Path = "C:\<какая-то папка на агенте сборки>\gitleaks.exe" # Папка с самим приложением Gitleaks.exe, данный исполняемый файл предварительно нужно положить на агент сборки
[string] $Gitleaks_Conf_Repo = "https://<инстанс>/tfs/<коллекция>/<отдельный проект под валидационные скрипты>/_git/Gitleaks-PR" # Репозиторий с исходным кодом запускатора Gitleaks
[string] $Gitleaks_Reports_Path = "${reportPath}\Gitleaks-report.json" # полный путь до файла в который будут записаны сработки Gitleaks
[string] $Gitleaks_Conf_Path_Full = "${reportPath}\Gitleaks-PR\Gitleaks-prod.toml" # Конфиг Gitleaks с регулярками
[string] $Path = $env:BUILD_SOURCESDIRECTORY
 
# Инициализируем путь до папки, в которой хранится временно смерженный код с основной ветвью из PR
$folders = Get-ChildItem -Path $Path
[string] $Path_to_analyze = $folders[1] # Первая папка это папка с Gitleaks-PR, нас интересует 2-я
 
# Печатаем в лог значения переменных на всякий случай
Write-Host "##[group]Подготовка к сканированию"
Write-Host "Автор Gitleaks: https://github.com/gitleaks/gitleaks"
Write-Host "" # Печать пустой строки для красоты
Write-Host "##[command]Параметры запуска:"
Write-Host "Папка с отчетом Gitleaks = ${reportPath}"
Write-Host "Путь до Gitleaks на агенте сборки = ${Gitleaks_EXE_Path}"
Write-Host "Gitleaks_Conf_Path_Full = ${Gitleaks_Conf_Path_Full}"
Write-Host "Репозиторий валидационной сборки = ${Gitleaks_Conf_Repo}"
Write-Host "Файл куда сохраняется отчет Gitleaks = ${Gitleaks_Reports_Path}"
Write-Host "Папка, в которой искать секреты = ${Path_to_analyze}"
Write-Host "" # Печать пустой строки для красоты
 
# Формируем список параметров запуска Gitleaks:
[string] $GitleaksArguments = "detect --redact --report-path ${Gitleaks_Reports_Path} --source ${Path_to_analyze} --config ${Gitleaks_Conf_Path_Full} --no-banner"
Write-Host "##[debug]Запускаемая команда: ${Gitleaks_EXE_Path} ${GitleaksArguments}"
Write-Host "" # Печать пустой строки для красоты
Write-Host "##[endgroup]"
 
# Запускаем Gitleaks.exe
Write-Host "##[command]Версия Gitleaks:"
Start-Process -NoNewWindow -Wait -FilePath $Gitleaks_EXE_Path "version" # Вывод информации о версии Gitleaks, список релизов: https://github.com/gitleaks/gitleaks/releases
Write-Host "##[command]Запуск Gitleaks"
Start-Process -NoNewWindow -Wait -FilePath $Gitleaks_EXE_Path -ArgumentList $GitleaksArguments # Запускаем Gileaks с параметрами

Примерный скрипт принятия решения

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

  1. Найден секрет — завершить валидационную сборку с ошибкой;

  2. Репозиторий может быть внесен в исключения — валидационная сборка завершается успешно с ошибкой, но не блокируется слияние ветвей. Добавляется комментарий к запросу на вытягивание с полезной информацией;

  3. При отсутствии секретов во вносимом коде — валидационная сборка успешно завершается.

Попробуем описать такую логику:

Gitleaks-failer.ps1
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()] # -report-path - обязательный параметр это путь до папки с отчетом сканирования на секреты
    $reportPath = "",
     
    [ValidateNotNullOrEmpty()]
    [uri]$instance = $($env:SYSTEM_COLLECTIONURI),
 
 
    [ValidateNotNullOrEmpty()]
    $prID = $($env:SYSTEM_PULLREQUEST_PULLREQUESTID),
 
    [ValidateNotNullOrEmpty()]
    [ValidatePattern("\d.\d")]
    $apiVersion = "3.2",
 
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    $tfsToken,
 
    [ValidateNotNullOrEmpty()]
    $repositoryID = $($env:BUILD_REPOSITORY_ID)
)
[string] $Gitleaks_Reports_Path = "${reportPath}\Gitleaks-report.json"
 
# Загружаем отчет с агента сборки
[string]$json = Get-Content $Gitleaks_Reports_Path
$json = $json | ConvertFrom-Json | ConvertTo-Json
 
# Проверяем отчет на пустоту
if($json.Length -lt 10) # По идее можно заменить на StartsWith("[]")
{
    Write-Host "##[section]Секреты не выявлены, проверка пройдена успешно."
}
else # В отчете более 10 символов = какая-то сработка точно есть
{
    Write-Host "##[error]Выявленные секреты:"
    Write-Host "##[group]Развернуть"   
    Write-Host $json # Выгружаем в лог сработки с секретами в маскированном виде (REDACTED)
    Write-Host "##[endgroup]"
 
    # Подсвечиваем разработчикам что делать, если выявлен секрет:
    Write-Host "##[error]Обнаружены секреты, валидационный билд будет помечен как неуспешный."
    Write-Host "##[error]Поиск секретов завершен, если конвейер упал, значит, возможен один из вариантов для текущего запроса на вытягивание:"
    Write-Host "##[error] - обнаружен реальный секрет в открытом виде (см. этап 'Поиск секретов' выше либо в артефактах билда в формате JSON);"
    Write-Host "##[error] - секрет уже не актуален;"
    Write-Host "##[error] - сработка является ошибочной и требует внесения в исключения."
    Write-Host "##[error]В любом случае см. инструкцию: https://<инстанс вики>/Gitleaks-PR+FAQ чтобы понять как действовать дальше."
 
    #Write-Host "##[command]Определение файла конфигурации" -ForegroundColor Cyan
 
    $project = $($env:SYSTEM_TEAMPROJECT).ToLower()
    $repo = $($env:BUILD_REPOSITORY_NAME).ToLower()
    #Write-Host "Project: ${project}"
    #Write-Host "Repo: ${repo}"
     
    # Определяем файл конфигурации по имени файлов в папке
    if (Test-Path "$($env:SYSTEM_DEFAULTWORKINGDIRECTORY)/Gitleaks-PR/config/gitleaks-$project.json")
    {
        $configFile = "gitleaks-$project.json"
    }
    else
    {
        $configFile = "gitleaks-default.json"
    }       
    Write-Host "##[command]Обнаружен конфиг декорирования: $configFile" -ForegroundColor Cyan
    $config = Get-Content "$($env:SYSTEM_DEFAULTWORKINGDIRECTORY)/Gitleaks-PR/config/$configFile" | ConvertFrom-Json      
 
    [bool] $isFailAllValidationBuilds = $config.'FailAllValidationBuildsInProject'
    if ($isFailAllValidationBuilds) # Можно фейлить все сборки если найден секрет
    {
        Write-Host "##[command]Согласно конфигу ${configFile} разрешено фейлить все валидационные сборки при обнаружении секретов"
        # Фейлим сборку с красивой ошибкой
        Write-Host "##vso[task.logissue type=Error;]Обнаружены секреты в исходном коде"
        Write-Host "##vso[task.complete result=Failed;]"
    }
    else # Раз нельзя фейлить все, то проверяем есть ли репозиторий в исключении
    {
        # Считываем список репозиториев в исключении, их не блокируем, все остальные будем лочить
        [bool] $isExcluded = $false
        [string] $ExcludetRepos_string = $config.'NotFailPRIntoRepos'
        $separator = @(",")
        $ExcludetRepos = $ExcludetRepos_string.Split($separator)
 
        Write-Host "##[debug]В проекте ${project} следующие репозитории в исключении: ${ExcludetRepos_string}"
 
        foreach ($ExcludetRepo in $ExcludetRepos)
        {
            if($ExcludetRepo -eq $repo)
            {
                $isExcluded = $true # Репозиторий внесен в исключения и даже при наличии секретов, валить сборку нельзя
                break
            }
        }
 
        # Пробуем оставить комментарий к PR чтобы капать на мозг разработчику о том, что у него есть секреты в коде и это нарушает политику ИБ
        #$prID = $($env:SYSTEM_PULLREQUEST_PULLREQUESTID)
        if ($prID -eq $null)
        {
            Write-Host "##vso[task.logissue type=Warning;] Не удалось определить ID запроса на вытягивание (PullRequest ID). Вероятно, валидационная сборка запущена напрямую из конвейера, а не из запроса на вытягивание." -ForegroundColor Yellow
            #Write-Host "##vso[task.complete result=SucceededWithIssues;]"
            #exit
        }
        else # PullRequest ID имеется (конвейер запущен из PR), оставляем комментарий
        {
            #Write-Host "##[command]Комментирование PR по факту выявления секретов"
            . $PSScriptRoot\Classes\PullRequest.ps1
            $pr = [PullRequest]::new($prID, $repositoryID, $instance, $apiVersion, $tfsToken)
 
            [string] $PR_message = "Во вносимом коде выявлены секреты, перечень найденных секретов см. в результатах сборки 'GitLeaks - Поиск секретов'.<br/>См. инструкцию [Gitleaks-PR+FAQ](https://wiki.corp.ingos.ru/display/SB/Gitleaks-PR+FAQ) чтобы понять как удалять секреты из исходного кода либо обрабатывать ошибочные сработки."
            $responseCommen = $pr.postNewThread($PR_message)
            if ($responseCommen.StatusCode -eq 200)
            {
                Write-Host "##[debug]Комментарий к PR успешно оставлен"
            }
        }
 
        if($isExcluded) # Секреты обнаружены, но репозиторий проекта в исключении
        {
            Write-Host "Согласно конфигу ${configFile} репозиторий ${repo} в исключении, валидационная сборка будет завершена с предупреждением"
            Write-Host "##vso[task.logissue type=Warning;] Обнаружены секреты в исходном коде, однако, репозиторий ${repo} в исключении, сборка не блокируется" -ForegroundColor Yellow
            Write-Host "##vso[task.complete result=SucceededWithIssues;]"
        }
        else # Секреты найдены, а сам репозиторий не в исключении
        {
            # Фейлим сборку с красивой ошибкой
            Write-Host "##vso[task.logissue type=Error;]Обнаружены секреты в исходном коде"
            Write-Host "##vso[task.complete result=Failed;]"
        }
    }
}

Результаты сборки

Рассмотрим визуальную составляющую возможных вариантов проверки вносимого кода на наличие секретов для разработчика.

Найден секрет

Найден секрет либо найдена ошибочная сработка, это приводит к неуспешному выполнению валидационной сборки:

Валидационная сборка при наличии секретов
Валидационная сборка при наличии секретов

При этом завершить запрос на слияние не удастся:

Невозможность завершения запроса на вытягивание
Невозможность завершения запроса на вытягивание

Найден секрет, но репозиторий в исключениях

Допустим, что во вносимом коде найден секрет либо найдена ошибочная сработка, но сам репозиторий в конфигурационном файле внесен в исключения:

Пример конфига для ведения реестра исключенных репозиториев
Пример конфига для ведения реестра исключенных репозиториев

На изображении выше замазаны наименования проектов Azure Devops, под каждый проект делается свой файл с исключениями.

Пример конфигурационного файла по умолчанию (gitleaks-default.json):

# gitleaks-default.json
{
  "FailAllValidationBuildsInProject": false, # Игнор исключений ниже
  "NotFailPRIntoRepos": "" # Список репозиториев в проекте через запятую, валидационные сборки в PR в которые не подлежат фейлингу, только успешное завершение, но с ошибкой
}

Валидационная сборка будет успешно завершена с предупреждением:

Валидационная сборка при наличии секретов и репозиторий в исключениях
Валидационная сборка при наличии секретов и репозиторий в исключениях

Сам же запрос на вытягивание выглядит довольно мирно:

Успешное прохождение валидационной сборки завершенной с ошибкой
Успешное прохождение валидационной сборки завершенной с ошибкой

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

Пример простейшего комментария от бота
Пример простейшего комментария от бота

Как видно, комментарий содержит:

  1. Уведомление о проблеме;

  2. Ссылку на wiki с описанием действий разработчика в случае появления такого комментария.

Для удобства разработчика из отчета Gitleaks можно достать номер строки из feature-ветви в которой найдена сработка и в тексте комментария добавить гиперссылку прямо на исходный код с секретом.

В случае, если для защищаемой ветви дополнительно настроен параметр «Проверка разрешения комментария»:

Требование обработки комментариев к запросам на вытягивание
Требование обработки комментариев к запросам на вытягивание

То разработчику придется выполнить управление комментарием:

Обработка комментария при нахождении секрета в запросе на вытягивание
Обработка комментария при нахождении секрета в запросе на вытягивание

Поскольку теперь завершить запрос на вытягивание без обработки комментария нельзя:

Отображение ошибки о необходимости обработать комментарий по найденному секрету
Отображение ошибки о необходимости обработать комментарий по найденному секрету
Ошибка при попытке завершения запроса на вытягивание
Ошибка при попытке завершения запроса на вытягивание

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

Секреты не обнаружены

Если по результату валидационной сборки не выявлены секреты, то результат сборки следующий:

Валидационная сборка в которой секреты не выявлены
Валидационная сборка в которой секреты не выявлены

Разработчик может спокойно завершить запрос на вытягивание.

2. Файлы конвейера

Чтобы создать конвейер, который будет запускать шаблон заданий из pipeline.yml нам необходимо в каждый защищаемый репозиторий добавить YML-файл конвейер (gitleaks-pipeline.yml) который вызывает шаблон задач из pipeline.yml:

Конвейер в структуре защищаемого репозитория
Конвейер в структуре защищаемого репозитория

Поскольку для того, чтобы в каждый репозиторий в вашем Azure DevOps Server добавить такой файл gitleaks-pipeline.yml, вам придется выполнить множество действий, рекомендуется разработать скрипты оркестрации.

Примерное содержимое такого конвейера (gitleaks-pipeline.yml):

gitleaks-pipeline.yml
name: "Gitleaks - Поиск секретов"
# Continious integration отключен
trigger: none
 
resources:
  repositories:
  - repository: Gitleaks-PR
    type: git
    # проект \ репозиторий
    name: <отдельный проект под валидационные скрипты>/Gitleaks-PR
 
jobs:
# Берем конвейер с названием pipeline.yml
# https://<инстанс Azure DevOps Server>/tfs/<коллекция>/<отдельный проект под валидационные скрипты>/_git/Gitleaks-PR?path=%2Fpipeline.yml&version=GBmaster
- template: pipeline.yml@Gitleaks-PR
# собственно тут и происходит вызов всех
# необходимых шаблов путем их загрузи из
# шаблона в <отдельный проект под валидационные скрипты>/Gitleaks-PR

3. Конвейеры

Для каждого репозитория, запросы на вытягивания в который требуется анализировать на наличие секретов, необходимо создать конвейер ссылающийся на файл gitleaks-pipeline.yml. Такой конвейер поместить в отдельную папочку, например, с названием Gitleaks:

Папка \Gitleaks в защищаемом проекте с конвейером валидационной сборки
Папка \Gitleaks в защищаемом проекте с конвейером валидационной сборки

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

4. Политика защиты ветвей

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

Параметры валидационной сборки "Gitleaks - Поиск секретов" у ветви master защищаемого репозитория
Параметры валидационной сборки "Gitleaks - Поиск секретов" у ветви master защищаемого репозитория

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

5. FAQ для разработчиков

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

Инструкция для разработчика в логе валидационной сборки
Инструкция для разработчика в логе валидационной сборки

Пример такого FAQ:

Оглавление страницы <WIKI >/Gitleaks-PR+FAQ на wiki
Оглавление страницы <WIKI >/Gitleaks-PR+FAQ на wiki
 Пример инструкции разработчику из FAQ
Пример инструкции разработчику из FAQ

5. FAQ для безопасника

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

ИнгоКошка по имени Фича изучает требования ДИТ к качеству разрабатываемого ПО
ИнгоКошка по имени Фича изучает требования ДИТ к качеству разрабатываемого ПО

Как вариант, некоторые компании для удобства дают возможность разработчикам самим вносить свои репозитории в списки исключений либо перекладывают данную активность на security champion-ов.

Итог

На первый взгляд один раз запустить Gitleaks просто, другое дело встроить его в проверку основных (текущих) и релизных ветвей, и выстроить целый процесс это совсем другой — долгий, но увлекательный процесс.

Итого, какие возможны проблемы при встраивании поиска секретов в запросах на вытягивание:

  1. Это долго при наличии хотя бы сотни репозиториев, требуется разработать оркестратор для встраивания;

  2. Если добавлять в репозитории gitleaks-pipeline.yml честно, через запросы на вытягивание, которые утверждаются командами разработки, то это отнимает очень много времени;

  3. Необходимо продумать схему работы разработчика над упавшей валидационной сборкой:

    1. Если завершать сборку с ошибками просто запуская Gitleaks без обработки отчета и комментирования запросов на вытягивание, то разработчик растеряется и разгневается;

    2. Разработка обработчика отнимает больше времени.

  4. Необходимо первично выполнить сканирование всех репозиториев на наличие секретов, в единый конфигурационный файл Gitleaks внести исключения, временные как-то пометить;

  5. Не все команды работают по одной методологии разработки и просто так встроить поиск секретов не удастся, например, команды работающие проектно. Если им на начале этапа утверждения требований ИБ к проекту не было заложено устранение секретов в коде, могут совсем отказать в сотрудничестве, устранение секретов в коде до конца проекта и не начнется;

  6. Необходимо иметь готовый сервис хранения секретов, например, HashiCorp Vault, иначе не получится вырезать секреты, ведь их некуда складывать, а предварительное внедрение HashiCorp Vault с правильно построенным регламентом его работы же может занять месяцы или годы;

  7. Некоторое легаси может вообще не подлежать переработке ради устранения проблемы с секретами;

  8. Специалисты по безопасности должны быть готовы ответить на любой вопрос по вопросу управления секретами;

  9. Нужен построенный процесс управления конфигурационным файлом Gitleaks для внесения в него исключений либо новых регулярных выражений для поиска неизвестных для Gitleaks секретов;

  10. Не получится встроить Gitleaks в блокирующем режиме в тех репозиториях, в которых секреты ещё не были вырезаны из кода;

  11. Помните, удаляя секрет из кода, он остается в истории и Gitleaks всё равно его найдет, поэтому следует проработать инструкцию для команд по окончательному удалению секретов из истории коммитов, см. Removing sensitive data from a repository и BFG Repo-Cleaner by Rtyley

Было бы интересно узнать мнение сообщества о том:

  1. С какими трудностями столкнулись в процессе анализа запросов на вытягивание на наличие секретов;

  2. Охотно ли команды исправляют уже ранее найденные секреты.

Другие мои статьи по безопасной разработке

Другие мои кейсы в части безопасности разработки смотри в статьях:

  1. Дерево атак на исходный код в Azure Repos (руководство по защите от атак направленных на компрометацию исходного кода и выбору мер защиты);

  2. Шпаргалка по сегментации приложений от OWASP (автор шпаргалки);

  3. Небезопасная разработка в Github (примеры публичных ошибок разработчиков);

  4. История утечки персональных данных через Github (пример публичных ошибок одного разработчика).

Комментарии (2)


  1. razoryoutub
    19.07.2023 11:46
    +1

    Пример неуспешной проверки запроса на вытягивание

    Имелся ввиду pull request видимо?


    1. Protos Автор
      19.07.2023 11:46

      Да