Branch permissions granular: protegendo paths específicos

1. Introdução à proteção granular de branches

1.1. O problema da proteção tudo-ou-nada

Em repositórios Git tradicionais, a proteção de branches opera no modelo binário: uma branch está protegida ou não. Quando ativada, todas as alterações na branch principal exigem revisão, independentemente do conteúdo. Esse modelo "tudo-ou-nada" cria riscos significativos em cenários onde paths específicos dentro do repositório são críticos — como arquivos de CI/CD, secrets de infraestrutura ou configurações de deploy.

Considere um cenário onde um desenvolvedor front-end modifica acidentalmente um arquivo .gitlab-ci.yml junto com uma correção de CSS. Sem proteção granular, essa alteração na pipeline pode passar despercebida em uma revisão focada apenas no código visual.

1.2. Path-based permissions vs. branch-level permissions tradicionais

As permissões baseadas em path diferem fundamentalmente das regras tradicionais de branch. Enquanto as regras de branch protegem a branch como um todo (exigindo pull requests, revisões ou status checks), as permissões por path permitem definir políticas específicas para diretórios ou arquivos individuais dentro da mesma branch.

# Exemplo: Proteção tradicional vs. granular
# Tradicional: Toda alteração na branch main requer 2 aprovações
# Granular: Alterações em config/ exigem aprovação do time de DevOps
#          Alterações em src/ exigem aprovação do time de desenvolvimento

1.3. Casos de uso típicos

Paths que frequentemente exigem proteção granular incluem:

  • infra/ ou deploy/: Configurações de infraestrutura e scripts de deploy
  • .github/workflows/ ou .gitlab-ci.yml: Arquivos de CI/CD
  • secrets/ ou config/: Arquivos com segredos ou configurações sensíveis
  • database/migrations/: Migrações de banco de dados que podem causar downtime

2. Mecanismos nativos de proteção por path no Git

2.1. .gitattributes e git diff

O Git oferece mecanismos nativos para detectar alterações em paths específicos, embora não forneça proteção automática. O arquivo .gitattributes pode definir atributos por path, e o comando git diff permite filtrar alterações:

# Detectar alterações apenas em paths específicos
git diff --name-only main..feature-branch | grep '^infra/'

# Usar diff-filter para identificar tipos de alteração
git diff --diff-filter=M --name-only main..feature-branch | grep '\.yml$'

2.2. Hooks do lado do servidor

Hooks server-side como pre-receive e update podem implementar validações de path. Um exemplo de hook update que bloqueia alterações em paths sensíveis:

#!/bin/bash
# Hook update: Bloqueia pushes que modificam paths críticos
refname="$1"
oldrev="$2"
newrev="$3"

# Paths proibidos sem aprovação específica
PROTECTED_PATHS=("infra/" "secrets/" "config/production.yml")

for path in "${PROTECTED_PATHS[@]}"; do
    if git diff --name-only $oldrev..$newrev | grep -q "^$path"; then
        echo "ERRO: Alterações em $path requerem aprovação do time de infraestrutura."
        exit 1
    fi
done
exit 0

2.3. Limitações dos hooks

Apesar de funcionais, hooks server-side apresentam limitações significativas:

  • Manutenção manual e propensa a erros
  • Falta de integração visual com a interface do repositório
  • Dificuldade de aplicar regras diferentes para diferentes branches
  • Ausência de logs centralizados de tentativas de bypass

3. Implementação com plataformas modernas

3.1. GitHub: CODEOWNERS e regras de branch

O GitHub oferece suporte nativo a path permissions através do arquivo CODEOWNERS combinado com regras de branch:

# .github/CODEOWNERS
# Time de infraestrutura é responsável por paths específicos
infra/ @time-devops
config/deploy.yml @time-devops
.github/workflows/ @time-qa

# Time de segurança revisa alterações em secrets
secrets/ @time-seguranca

Para forçar a revisão, configure a regra de branch "Require review from Code Owners".

3.2. GitLab: Merge request approvals com regras de path

No GitLab, as regras de aprovação podem ser configuradas por path:

# Configuração no GitLab UI ou via API
# Crie uma regra de aprovação que exige:
# - 2 aprovações do grupo "devops" para alterações em infra/
# - 1 aprovação do grupo "security" para alterações em secrets/

3.3. Bitbucket: Branch permissions avançadas

O Bitbucket permite criar permissões de branch com restrições por path:

# Configuração no Bitbucket
# 1. Acesse Settings > Branch permissions
# 2. Crie uma regra para a branch main
# 3. Adicione "Path-based restrictions" para infra/ e config/
# 4. Defina grupos que podem modificar esses paths

4. Estratégia de CODEOWNERS para paths sensíveis

4.1. Sintaxe de arquivos CODEOWNERS

O arquivo CODEOWNERS segue uma sintaxe simples baseada em padrões de path:

# Padrões de path e owners correspondentes
# Cada linha define um padrão e os usuários/equipes responsáveis

# Owner padrão para todo o repositório
* @time-core

# Owner específico para diretório de documentação
docs/ @time-tech-writer

# Owner para múltiplos diretórios
infra/ config/deploy/ @time-devops

# Owner para arquivos específicos
.env.example @time-seguranca

4.2. Combinando CODEOWNERS com required reviews

Para que o CODEOWNERS seja efetivo, é necessário configurar regras de branch que exijam revisão dos code owners:

# Configuração no GitHub (via UI ou API)
# 1. Settings > Branches > Add rule
# 2. Nome da branch: main
# 3. Habilitar "Require a pull request before merging"
# 4. Habilitar "Require review from Code Owners"
# 5. Habilitar "Dismiss stale pull request approvals when new commits are pushed"

4.3. Boas práticas para CODEOWNERS

  • Evitar overlaps: Quando dois padrões correspondem ao mesmo path, o último padrão no arquivo tem precedência
  • Definir owners por subdiretórios: Seja específico para evitar ambiguidades
  • Usar equipes em vez de usuários individuais: Facilita a manutenção quando membros mudam

5. Integração com status checks e CI/CD como gate de path

5.1. Criando status checks que analisam paths alterados

É possível criar status checks que analisam quais paths foram modificados e aplicam regras específicas:

# Exemplo de script para GitHub Actions
name: Path Security Check
on: pull_request

jobs:
  check-sensitive-paths:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Check for sensitive path changes
        run: |
          CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}..HEAD)
          SENSITIVE_PATHS=("infra/" "secrets/")

          for path in "${SENSITIVE_PATHS[@]}"; do
            if echo "$CHANGED_FILES" | grep -q "^$path"; then
              echo "Alterações detectadas em path sensível: $path"
              echo "Requer aprovação do time de DevOps."
              exit 1
            fi
          done

5.2. Workflows de CI que bloqueiam merges

Um workflow completo pode bloquear merges se paths proibidos forem modificados sem aprovação:

name: Block Sensitive Changes

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  validate-paths:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Validate changed paths
        id: validate
        run: |
          CHANGED=$(git diff --name-only origin/main..HEAD)
          echo "Arquivos alterados:"
          echo "$CHANGED"

          # Verificar se há alterações em paths críticos
          if echo "$CHANGED" | grep -qE '^(infra/|config/production)'; then
            echo "CRITICAL_PATH_CHANGED=true" >> $GITHUB_ENV
          fi

      - name: Notify DevOps team
        if: env.CRITICAL_PATH_CHANGED == 'true'
        run: |
          echo "Alterações críticas detectadas. Notificando time de DevOps."
          # Aqui poderia enviar notificação via Slack, email, etc.

5.3. Exemplo prático: Pipeline bloqueando alterações em infra/

# .github/workflows/infra-guard.yml
name: Infra Guard

on:
  pull_request:
    paths:
      - 'infra/**'
      - 'config/deploy/**'

jobs:
  require-devops-approval:
    runs-on: ubuntu-latest
    steps:
      - name: Check for DevOps approval label
        run: |
          if [[ "${{ github.event.pull_request.labels }}" != *"devops-approved"* ]]; then
            echo "Este PR modifica infraestrutura. Adicione o label 'devops-approved' após revisão."
            exit 1
          fi

6. Políticas de merge condicionais por path

6.1. Definindo regras de merge que exigem revisores específicos

Plataformas modernas permitem criar regras de merge condicionais baseadas em paths:

# Exemplo de configuração no GitHub
# Usando branch protection rules + CODEOWNERS
# 1. Crie a regra de branch para main
# 2. Em "Require a pull request before merging", configure:
#    - Required approvals: 2
#    - Dismiss stale reviews: true
#    - Require review from Code Owners: true
# 3. Em "Require status checks", adicione o check de path validation

6.2. Uso de required_pull_request_reviews com path patterns

# Configuração via API do GitHub
curl -X PUT \
  -H "Authorization: token $TOKEN" \
  -H "Accept: application/vnd.github.v3+json" \
  https://api.github.com/repos/owner/repo/branches/main/protection \
  -d '{
    "required_pull_request_reviews": {
      "required_approving_review_count": 2,
      "require_code_owner_reviews": true
    },
    "required_status_checks": {
      "strict": true,
      "contexts": ["Path Security Check"]
    }
  }'

6.3. Automatizando a atribuição de revisores

É possível automatizar a atribuição de revisores baseada em paths modificados:

# Script para atribuir revisores automaticamente
# Executado como GitHub Action ou webhook

CHANGED_FILES=$(git diff --name-only origin/main..HEAD)

if echo "$CHANGED_FILES" | grep -q "^infra/"; then
    ASSIGN_REVIEWERS+=("time-devops")
fi

if echo "$CHANGED_FILES" | grep -q "^secrets/"; then
    ASSIGN_REVIEWERS+=("time-seguranca")
fi

# Atribuir revisores via API
for reviewer in "${ASSIGN_REVIEWERS[@]}"; do
    curl -X POST \
        -H "Authorization: token $TOKEN" \
        -H "Accept: application/vnd.github.v3+json" \
        https://api.github.com/repos/owner/repo/pulls/$PR_NUMBER/requested_reviewers \
        -d "{\"team_reviewers\":[\"$reviewer\"]}"
done

7. Monitoramento e auditoria de permissões por path

7.1. Logs de alterações em paths protegidos

O Git fornece ferramentas poderosas para auditar alterações em paths específicos:

# Listar todos os commits que modificaram paths protegidos
git log --all --oneline -- 'infra/' 'secrets/'

# Verificar quem modificou um arquivo específico
git log --format="%h %an %ad %s" -- 'config/deploy.yml'

# Usar git blame para ver a última modificação linha a linha
git blame -L 10,20 config/deploy.yml

7.2. Ferramentas de auditoria

Para detectar bypasses ou permissões excessivas:

# Script de auditoria: detecta merges que bypassaram proteções
# Verifica pull requests mesclados sem revisão de code owners
git log --merges --format="%h %an %ad" --first-parent main

# Verificar alterações em paths protegidos que não passaram por review
# (requer integração com API da plataforma)

7.3. Revisão periódica de regras de path permissions

Estabeleça um processo de revisão periódica:

# Checklist de revisão trimestral
# 1. Verificar se novos paths críticos foram adicionados
# 2. Remover regras para paths que não são mais sensíveis
# 3. Atualizar CODEOWNERS com mudanças de equipe
# 4. Testar regras com branches de exemplo
# 5. Revisar logs de tentativas de bypass

8. Considerações finais e boas práticas

8.1. Equilíbrio entre segurança e agilidade

Proteger paths desnecessariamente pode criar gargalos no desenvolvimento. Avalie cuidadosamente quais paths realmente precisam de proteção granular. Uma boa prática é começar com paths críticos (CI/CD, secrets, configurações de produção) e expandir conforme necessário.

8.2. Documentação das regras para a equipe

Mantenha documentação clara sobre as regras de path permissions:

# Exemplo de documentação no README.md
## Path Permissions

### Paths Protegidos
- `infra/` - Requer aprovação do time de DevOps
- `secrets/` - Requer aprovação do time de Segurança
- `.github/workflows/` - Requer aprovação do time de QA

### Como Solicitar Alterações
1. Crie um pull request
2. Adicione o label correspondente ao path alterado
3. Aguarde a revisão do time responsável

8.3. Teste das políticas com branches de exemplo

Antes de aplicar regras em produção, teste em branches de exemplo:

# Fluxo de teste
# 1. Crie uma branch de teste: git checkout -b test-path-permissions
# 2. Modifique um arquivo em path protegido
# 3. Crie um pull request para main
# 4. Verifique se as regras de proteção são aplicadas corretamente
# 5. Ajuste as regras conforme necessário
# 6. Delete a branch de teste

A proteção granular de paths no Git é uma prática essencial para equipes que lidam com repositórios complexos contendo diferentes tipos de ativos. Ao implementar essas estratégias, você equilibra segurança e produtividade, garantindo que alterações críticas recebam a atenção necessária sem burocratizar o desenvolvimento de código não sensível.

Referências