Na última parte desta série, concluímos a implantação da infraestrutura para um pipeline de desenvolvimento seguro (DevSecOps): instalamos GitLab, Nexus, HashiCorp Vault, Dependency-Track e DefectDojo, preparamos uma máquina virtual separada com ferramentas de segurança e confirmamos que todos os serviços estavam rodando com sucesso. No entanto, instalar os serviços é apenas metade do trabalho. Agora, eles precisam ser configurados e preparados para trabalhar juntos. Sem isso, o GitLab permanecerá apenas um GitLab, o Vault apenas um repositório de segredos, e o DefectDojo e o Dependency-Track serão apenas interfaces web bonitas sem utilidade prática. Nesta parte, focaremos na configuração básica. Configuraremos o GitLab e o GitLab Runner, prepararemos o Nexus para receber artefatos, ensinaremos o Vault a confiar no GitLab através da autenticação JWT e criaremos as entidades necessárias no DefectDojo e Dependency-Track.
Aviso Legal É importante estabelecer um acordo inicial: este ciclo de artigos não substitui os requisitos de metodologias sérias de desenvolvimento seguro como GOST, OWASP SAMM, BSIMM, entre outras. É, antes, um exemplo prático de como organizar um pipeline de desenvolvimento seguro de software com investimento mínimo. Tudo o que é descrito no ciclo é puramente minha experiência pessoal, os percalços que enfrentei e os resultados de longas noites debruçado sobre o teclado. Não representa a posição oficial dos meus empregadores atuais ou anteriores. Não encare este artigo como um guia para cópia cega. Teste tudo em ambientes de teste, faça backups e aborde os experimentos com a cabeça fria. Erros são possíveis, e não se deve ter medo de cometê-los. O principal é tirar conclusões e construir gradualmente processos que serão mais confiáveis do que os de ontem.
Para ser franco, não configurei HTTPS dentro do laboratório doméstico. Há várias razões para isso: o ambiente é para aprendizado, não para produção; meu roteador não suporta a configuração de DNS local; para um esquema completo com uma autoridade certificadora interna, seria necessário levantar uma máquina virtual adicional, e restam poucos recursos livres – o ambiente atual já consome 22 GB de RAM. Portanto, por enquanto, trabalharemos via HTTP. Mas trocamos as senhas imediatamente – isso é sagrado.
GitLab
"Cada casa tem sua ordem", pensei, e a primeira coisa que fiz foi organizar a estrutura do GitLab. Após o primeiro login com a senha root (que descobri com o comando sudo cat /etc/gitlab/initial_root_password na máquina virtual com o serviço), troquei imediatamente a senha pela minha. Em seguida, criei um grupo de repositórios para projetos de serviço, templates de pipeline, configurações de IaC (Infrastructure as Code) e para as aplicações em desenvolvimento. Se não se tratar de um ambiente de demonstração, mas de desenvolvimento real, é melhor dividir os projetos em vários grupos de acordo com sua finalidade.
Em seguida, carreguei o código da nossa aplicação vulnerável no repositório. Para executar os pipelines, precisaremos do GitLab Runner – um agente que recebe tarefas do GitLab e executa as etapas correspondentes do pipeline CI/CD. Vamos instalá-lo na quinta máquina virtual (sec-vm), pois ela já possui Docker e todas as ferramentas de segurança necessárias. Após a instalação, registrei dois runners: um com executor Docker e outro com executor Shell. Para eles, especifiquei as tags docker, linux, devsecops e shell, linux, local, respectivamente. Também ativei o auto-start para que, após a reinicialização da máquina virtual, os runners retornem automaticamente ao trabalho.
Adiciono o repositório:
bashcurl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
Instalo o pacote:
bashsudo apt install -y gitlab-runner
Registro o runner (interativamente ou através de parâmetros):
bashsudo gitlab-runner register
Durante o registro, precisarei de:
- URL do GitLab – no meu caso,
http://192.168.0.201:9090/. - Token de registro, que deve ser obtido no GitLab em
Admin Area → CI/CD → Runners → Register an instance runner. - Descrição – um nome arbitrário para o runner, como
docker-runneroushell-runner. - Tags:
docker,linux,devsecops(essas tags serão usadas posteriormente nos pipelines). - Executor – o tipo de executor. No nosso caso, precisaremos registrar dois runners: um com executor
dockere outro com executorshell. - Imagem padrão:
alpine:latest. Ativo o auto-start:
bashsudo systemctl enable gitlab-runner
Verifico o status:
bashsudo systemctl status gitlab-runner
Após o registro, verifico se o runner apareceu na lista e tem status verde.
Nas configurações do repositório, proibi o push direto para o branch main – apenas através de Merge Request e apenas para mantenedores.
Variáveis CI/CD Para que o pipeline possa interagir com os outros serviços do nosso pipeline, ele precisará de alguns parâmetros: o endereço do Vault, os identificadores de projetos no Dependency-Track e o identificador do produto no DefectDojo. Para todos os projetos, adicionei duas variáveis globais:
| Variável | Valor | Proteção/Mascaramento |
|---|---|---|
VAULT_AUTH_ROLE | ci-role | Não proteger/mascarar |
VAULT_ADDR | http://192.168.0.203:8200 | Mascarar |
Elas podem ser adicionadas em Admin → CI/CD → Variables.
Para cada projeto, será necessário criar um conjunto próprio de variáveis:
| Variável | Valor | Proteção/Mascaramento |
|---|---|---|
DEPENDENCY_TRACK_PROJECT_DOCKER_UUID | UUID do projeto no qual analisarei a composição do contêiner Docker, obtido do Dependency-Track | Mascarar |
DEPENDENCY_TRACK_PROJECT_UUID | UUID do projeto obtido do Dependency-Track | Mascarar |
DOJO_PRODUCT_ID | ID do produto obtido do DefectDojo | Mascarar |
Essas variáveis são adicionadas em Settings → CI/CD → Variables do projeto específico.
Nexus
"O repositório é a cabeça de tudo", disse eu e comecei a organizar o Nexus. Após o login com a senha do arquivo /nexus-data/admin.password, troquei-a imediatamente. Para criar um repositório físico de dados, configuro os Blob Stores:
- Vou para
Administration → Repository → Blob Stores. - Crio Blob Stores separados para diferentes tipos de dados. Por exemplo,
docker-blob,npm-blob,pgsql-dump-blob. Isso permitirá gerenciar a localização dos arquivos no disco e simplificará o backup.
Crio um repositório para armazenar contêineres Docker. Caminho: Administration → Repository → Repositories → Create repository.
Para Docker (hosted):
- Escolho a receita
docker (hosted). - Em
Storage, seleciono o Blob Store criado anteriormente. - Em
Repository Connectors, mantenhoPath based routing, pois não terei um domínio e não poderei expor uma porta separada. O repositório estará acessível emhttp://192.168.0.202:8081/repository/docker/.
No âmbito da configuração de acesso para CI/CD, crio um usuário separado para o GitLab CI. Para isso:
- Crio a role
nx-deployment. Vou paraSecurity → Users → Roles(concedo permissões apenas para trabalhar com repositórios). Para imagens Docker, precisamos das seguintes permissões:
nx-repository-view-docker-*-read– leitura;nx-repository-view-docker-*-add– adição de novos artefatos;nx-repository-view-docker-*-edit– edição;nx-repository-view-docker-*-browse– visualização do conteúdo;
- Em seguida, vou para
Security → Users → Create local usere crio um novo usuário:
- Insiro o ID:
gitlab-ci. - Insiro a Senha: defina uma senha forte.
- Em
Roles, adiciononx-deployment(esta é a nossa role criada anteriormente). Agora o GitLab CI poderá fazer push de imagens e relatórios sem expor a senha no código.
HashiCorp Vault Para que os pipelines possam obter segredos do Vault de forma segura, precisamos ensiná-lo a confiar no GitLab. Para isso, configuraremos a autenticação JWT. Escondi o token root bem longe – ele é apenas para configuração inicial. Uma observação importante sobre a segurança do token root: nunca use o token root no trabalho diário, e muito menos em CI/CD! O token root é necessário apenas para a configuração inicial. Após a criação de políticas e roles, o acesso aos segredos deve ser feito apenas através delas.
Autenticação JWT
Na máquina virtual vault-vm, entro no contêiner com o Vault e faço login.
bashdocker exec -it vault sh export VAULT_ADDR=http://127.0.0.1:8200 vault login <root_token>
Ativo o motor kv-v2 no caminho secret e carrego nossos segredos lá (URLs e chaves de API do DefectDojo, Dependency-Track, Nexus).
bashvault secrets enable -path=secret kv-v2 vault kv put secret/dependency-track url="http://192.168.0.204:8081" api_key="" vault kv put secret/defectdojo url="http://192.168.0.204:9090" token="" vault kv put secret/nexus url="http://192.168.0.202:8081" docker-repo="docker" registry="192.168.0.202:8081" username="gitlab-ci" password=""
Em seguida, crio a política ci-policy com permissões de leitura para esses caminhos.
bashvault policy write ci-policy - <<EOF path "secret/data/defectdojo" { capabilities = ["read"] } path "secret/data/dependency-track" { capabilities = ["read"] } path "secret/data/nexus" { capabilities = ["read"] } EOF
E o mais interessante – configuro a autenticação JWT.
bashvault write auth/jwt/role/gitlab-role \ role_type="jwt" \ vault write auth/jwt/config \ oidc_discovery_url="http://192.168.0.201:9090" \ bound_issuer="http://192.168.0.201:9090" \ groups_claim="groups" \ policies="gitlab-policy" \ ttl=1h
Se aprofundarmos um pouco, dentro do comando definimos:
auth/jwt/role/gitlab-role– o caminho para a role.role_type="jwt"– indica explicitamente que a role usa JWT.bound_audiences– oaudobrigatório do token JWT. O GitLab emite tokens com umaudespecífico, que deve ser especificado.groups_claim– o campo que contém a lista de grupos do GitLab, para que o Vault possa mapeá-los para as políticas.policies– as políticas do Vault que serão atribuídas após a autenticação bem-sucedida.ttl– o tempo de vida do token do Vault obtido.
O GitLab CI é capaz de emitir um ID_TOKEN para cada job. Configurei o Vault para confiar nos tokens assinados pelo meu GitLab.
Foi aí que encontrei um obstáculo: a autorização não funcionava até que eu adicionasse o jwks_url e o default_role corretos à configuração JWT.
bashvault write auth/jwt/config \ jwks_url="http://192.168.0.201:9090/oauth/discovery/keys" \ default_role="ci-role"
Depois disso, tudo funcionou. O pipeline recebia um token, acessava o Vault, e o Vault lhe fornecia os segredos. Que beleza!
DefectDojo Agora, prepararemos o DefectDojo para receber os resultados da varredura. É para cá que nossas ferramentas de segurança enviarão as vulnerabilidades encontradas, portanto, primeiro configuraremos o acesso para CI/CD e depois criaremos um produto – essencialmente, um cartão da nossa aplicação, ao qual os resultados das verificações serão vinculados. Para começar, gero um token de API, que será necessário posteriormente para os pipelines do GitLab:
- No canto superior direito, clico no ícone do usuário.
- Clico no botão
Get API Key. - Copio o token gerado – este será o
DOJO_API_TOKENpara o GitLab. Em seguida, crio um produto. No menu esquerdo, selecionoProducts → Add Product. Preencho o nome e a descrição do produto, clico no botãoSubmit. E o mais importante – ativo a desduplicação global nas configurações do sistema, para que os defeitos de segurança não sejam duplicados em novas varreduras. É uma ferramenta muito útil, caso contrário, você pode se afogar em bugs repetidos. No DefectDojo, vamos paraSettings → System Settings. Encontro a opçãoDeduplicate findingseDelete duplicatese a ativo. Isso ativará o algoritmo de desduplicação para todas as importações futuras. Pronto, agora nossos scanners enviarão relatórios para cá.
Dependency-Track
Agora, preparamos o Dependency-Track para funcionar. Este serviço será responsável por analisar a composição da nossa aplicação e contêineres, rastrear vulnerabilidades em componentes utilizados e armazenar informações sobre os problemas encontrados.
"O grafo de dependências é como uma teia, você se perde", suspirei e criei um projeto no Dependency-Track com a versão 1.0.0.
Em seguida, em Administration → Access Management → Teams, criei a equipe Automation com o tipo Automation e as permissões: BOM_UPLOAD, PROJECT_CREATION_UPLOAD, VIEW_PORTFOLIO.
Gerei uma chave de API e a salvei no Vault. Agora o GitLab CI poderá carregar arquivos SBOM, e o Dependency-Track atualizará automaticamente as vulnerabilidades e construirá gráficos bonitos. Configurei os projetos:
- Fui para
Projects → Create Project. - Especifiquei o nome e a versão. Foram criados 2 projetos – um para o SBOM do próprio software em desenvolvimento e outro para o SBOM do contêiner. Posteriormente, ao carregar o SBOM (Software Bill of Materials) via API, o Dependency-Track atualizará automaticamente os dados deste projeto.
- A partir de setembro de 2025, o serviço exige autenticação para acesso à API, portanto, para que o analisador funcione, é necessário registrar uma conta gratuita no site do OSS Index e inserir as credenciais obtidas nas configurações do Dependency-Track.
Conclusão
Neste ponto, a fase preparatória pode ser considerada concluída. Configuramos os principais serviços do futuro pipeline de desenvolvimento seguro: GitLab, Nexus, Vault, DefectDojo e Dependency-Track, e também os conectamos onde for necessário para automação futura. Na próxima parte, faremos o que tudo isso foi planejado: escreveremos o principal "pergaminho" do nosso reino de desenvolvimento seguro – o arquivo .gitlab-ci.yml, onde toda a lógica do pipeline será descrita. Também montaremos um pipeline completo com as etapas build, sbom, code-scan e upload, conectaremos templates comuns do repositório devsecops, aprenderemos a obter segredos do Vault via JWT e a enviar automaticamente os resultados do trabalho para Nexus, DefectDojo e Dependency-Track. Até a próxima parte do ciclo!
Links para as fontes originais Para um estudo mais aprofundado de cada tópico, aqui estão os links diretos para a documentação oficial:
- GitLab: GitLab Docs: Security Best Practices – recomendações gerais.
- Nexus: Sonatype Support: Nexus Repository Manager – documentação oficial.
- Vault: HashiCorp Developer: Vault Documentation – guia completo.
- DefectDojo: DefectDojo Documentation – documentação oficial do projeto.
- Dependency-Track: Dependency-Track Documentation – tudo sobre configuração e uso.
