
GCP
Engineering Handbook - Capitulo 09
Google Cloud Master Journey 2026
Engineering Handbook · Capítulo 09
Como organizei pacotes Java privados, criei um cache controlado para o Maven Central e usei um repositório virtual para definir precedência entre dependências internas e externas.
Este artigo faz parte da série Google Cloud Master Journey 2026, em que estou transformando laboratórios práticos em capítulos de um Engineering Handbook sobre Cloud Engineering, DevSecOps, arquitetura e segurança.
A proposta não é apenas registrar comandos executados. Quero transformar cada laboratório em um material que responda perguntas de engenharia:
Neste capítulo, o tema é gerenciamento seguro de dependências Java com Artifact Registry.
O laboratório usado como base foi Securing Container Builds (GSP1185).
Apesar do nome mencionar builds de contêiner, a prática concentra-se no gerenciamento de pacotes Maven que podem alimentar aplicações e imagens de contêiner posteriormente.
Durante o laboratório, construí três modos de repositório:
O resultado é uma arquitetura em que o cliente não precisa consultar diretamente várias origens.
Ao final do laboratório, o fluxo ficou assim:
Código Java
→ Maven
→ repositório virtual
→ pacote privado, quando existir
→ cache remoto, quando for dependência pública
→ Maven Central apenas em caso de cache miss
Também configuramos uma rota separada de publicação:
mvn deploy
→ Standard Repository
Essa separação é importante:
Quando uma aplicação executa:
mvn compile
ela não compila apenas o código escrito pela equipe.
O Maven pode baixar:
Uma dependência externa pode alterar o comportamento do build sem que o código da aplicação mude.
Por isso, controlar dependências não é apenas uma questão de velocidade.
É uma decisão de:
O Maven Central é essencial para o ecossistema Java, mas uma organização que depende diretamente de um registry público enfrenta alguns riscos operacionais:
Um Remote Repository não elimina a dependência do upstream.
Ele adiciona uma camada controlada entre o cliente e a fonte externa.
Artifact Registry possui diferentes modos de repositório.
Neste laboratório, utilizamos os três que formam o padrão mais importante para gerenciamento de dependências.
| Modo | Responsabilidade | Armazena conteúdo? | Origem do conteúdo |
|---|---|---|---|
| Standard | Pacotes privados publicados pela organização | Sim | Upload ou deploy direto |
| Remote | Proxy e cache de uma origem upstream | Sim, após solicitação | Maven Central |
| Virtual | Ponto único de acesso a upstreams | Não armazena diretamente | Standard e Remote |
Um detalhe arquitetural importante é que o modo do repositório não pode ser transformado depois de sua criação.
A escolha precisa ser feita no design inicial.
Standard Repository é o modo usado para armazenar artefatos privados diretamente.
No laboratório, criamos:
gcloud artifacts repositories create container-dev-java-repo \
--repository-format=maven \
--location=us-central1 \
--description="Java package repository for Container Dev Workshop"
O repositório criado tinha:
format: MAVEN
mode: STANDARD_REPOSITORY
location: us-central1
Esse repositório passa a ser a fonte controlada para bibliotecas internas.
Em uma organização, um pacote Maven privado pode representar:
Sem um registry interno, equipes podem recorrer a práticas frágeis:
Um Standard Repository transforma a biblioteca em um artefato versionado e consumível pelo ecossistema Maven.
O comando central foi:
gcloud artifacts print-settings mvn \
--repository=container-dev-java-repo \
--location=us-central1
Ele gera as configurações que precisam ser adicionadas ao projeto.
Três seções são especialmente importantes:
distributionManagement
repositories
build/extensions
Cada uma resolve uma responsabilidade diferente.
distributionManagementEssa seção define para onde o projeto publica seus artefatos.
<distributionManagement>
<snapshotRepository>
<id>artifact-registry</id>
<url>
artifactregistry://us-central1-maven.pkg.dev/PROJECT_ID/container-dev-java-repo
</url>
</snapshotRepository>
<repository>
<id>artifact-registry</id>
<url>
artifactregistry://us-central1-maven.pkg.dev/PROJECT_ID/container-dev-java-repo
</url>
</repository>
</distributionManagement>
Modelo mental:
mvn deploy
→ distributionManagement
→ destino de publicação
Essa seção não é a principal responsável por resolver dependências.
Ela define o destino do pacote produzido pelo projeto.
repositoriesEssa seção informa onde Maven pode procurar artefatos consumidos pela aplicação.
<repositories>
<repository>
<id>artifact-registry</id>
<url>
artifactregistry://us-central1-maven.pkg.dev/PROJECT_ID/container-dev-java-repo
</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
Modelo mental:
mvn compile
→ repositories
→ origens de leitura
A diferença é fundamental:
| Operação | Seção |
|---|---|
| Publicar o pacote | distributionManagement |
| Baixar dependências | repositories |
artifactregistry-maven-wagonO protocolo usado na URL não é HTTP comum:
artifactregistry://
O Maven precisa de um transport provider capaz de:
O laboratório adicionou:
<build>
<extensions>
<extension>
<groupId>com.google.cloud.artifactregistry</groupId>
<artifactId>artifactregistry-maven-wagon</artifactId>
<version>2.2.0</version>
</extension>
</extensions>
</build>
.mvn/extensions.xmlO laboratório também criou:
<extensions
xmlns="http://maven.apache.org/EXTENSIONS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 https://maven.apache.org/xsd/core-extensions-1.0.0.xsd">
<extension>
<groupId>com.google.cloud.artifactregistry</groupId>
<artifactId>artifactregistry-maven-wagon</artifactId>
<version>2.2.0</version>
</extension>
</extensions>
Esse arquivo é carregado como core extension antes do pom.xml.
Isso importa quando o projeto precisa resolver:
Colocar a extensão apenas no bloco <build> pode ser tarde demais para determinados cenários de resolução antecipada.
Depois da configuração, executamos:
mvn deploy -DskipTests
O fluxo foi:
O Artifact Registry armazenou mais do que um JAR isolado.
Um pacote Maven pode incluir:
.jar;pom.xml;O Standard Repository do laboratório foi criado sem política de versão específica.
Assim, ele aceita releases e snapshots.
Em produção, eu avaliaria separar:
maven-releases
maven-snapshots
Vantagens:
O repositório remoto foi criado como proxy do Maven Central:
gcloud artifacts repositories create maven-central-cache \
--project=${PROJECT_ID} \
--repository-format=maven \
--location=us-central1 \
--description="Remote repository for Maven Central caching" \
--mode=remote-repository \
--remote-repo-config-desc="Maven Central" \
--remote-mvn-repo=MAVEN-CENTRAL
O fluxo funciona assim:
Ele não é uma cópia completa do Maven Central.
O cache é preenchido sob demanda.
pacote nunca solicitado
→ não está no cache
Também não significa independência total do upstream.
Em um cache miss, a origem externa ainda precisa estar disponível e acessível.
O laboratório removeu o cache local do Maven:
rm -rf ~/.m2/repository
Depois compilou:
mvn compile
Isso obrigou o cliente a solicitar novamente as dependências.
Há dois caches diferentes:
~/.m2/repository;Confundir os dois pode gerar interpretações erradas durante troubleshooting.
Virtual Repository é um ponto único de acesso para múltiplos upstream repositories do mesmo formato.
No laboratório, ele combinou:
container-dev-java-repo
maven-central-cache
A política foi:
[
{
"id": "private",
"repository": "projects/PROJECT_ID/locations/us-central1/repositories/container-dev-java-repo",
"priority": 100
},
{
"id": "central",
"repository": "projects/PROJECT_ID/locations/us-central1/repositories/maven-central-cache",
"priority": 80
}
]
E o repositório foi criado:
gcloud artifacts repositories create virtual-maven-repo \
--project=${PROJECT_ID} \
--repository-format=maven \
--mode=virtual-repository \
--location=us-central1 \
--description="Virtual Maven Repo" \
--upstream-policy-file=policy.json
Sem o virtual, o cliente precisaria conhecer vários repositórios:
<repositories>
<repository>privado</repository>
<repository>remoto</repository>
</repositories>
Com o virtual:
<repositories>
<repository>
<id>artifact-registry</id>
<url>
artifactregistry://us-central1-maven.pkg.dev/PROJECT_ID/virtual-maven-repo
</url>
</repository>
</repositories>
Isso simplifica:
Não diretamente.
Ele funciona como uma camada de resolução.
O conteúdo continua nos upstreams:
pacote privado
→ Standard Repository
pacote público em cache
→ Remote Repository
Virtual Repository
→ interface e política de roteamento
Imagine que uma empresa usa internamente:
com.iisatech:security-core:1.4.0
O pacote existe apenas no repositório privado.
Se o cliente estiver configurado para consultar fontes privadas e públicas sem uma precedência confiável, um atacante pode tentar publicar em uma origem pública um pacote com coordenadas ou versão capazes de ser escolhidas pelo resolvedor.
O risco aumenta quando:
O laboratório atribuiu:
private priority: 100
central priority: 80
Quando o mesmo artefato pode ser resolvido por mais de um upstream, a prioridade controla qual origem deve prevalecer.
A prioridade maior do repositório privado reduz o risco de o pacote público competir com o pacote interno.
Virtual Repository é uma camada importante, mas não substitui todas as medidas.
Ainda é necessário:
pom.xml;Virtual Repository
≠ proteção completa da supply chain
Depois de entender o fluxo manual, construí um script Bash que:
PROJECT_ID e PROJECT_NUMBER;pom.xml;.mvn/extensions.xml;Check my progress;O script também gera arquivos de estudo:
pom.xml.lab-original
standard-repository-settings.txt
remote-repository-settings.txt
virtual-repository-settings.txt
effective-pom-standard.xml
policy.json
O laboratório demonstra os três modos, mas uma arquitetura de produção exige decisões adicionais.
Desenvolvedores e pipelines que publicam pacotes precisam de escrita.
Aplicações consumidoras normalmente precisam apenas de leitura.
publisher
→ roles/artifactregistry.writer
consumer
→ roles/artifactregistry.reader
Não daria permissão de escrita para todos os consumidores.
Em vez de conceder acesso amplo no projeto, eu usaria permissões específicas por repositório quando possível.
Exemplo:
team-platform
→ writer em platform-snapshots
release-pipeline
→ writer em platform-releases
application-teams
→ reader no virtual repository
Quando repositórios virtuais e upstreams estão em projetos diferentes, o agente de serviço do Artifact Registry do projeto virtual precisa ter acesso aos repositórios upstream.
Isso deve ser planejado explicitamente em uma arquitetura cross-project.
Uma arquitetura madura pode usar:
artifact-platform-project
application-build-project
production-runtime-project
O projeto de Artifact Registry torna-se uma plataforma compartilhada.
Um release publicado não deveria mudar silenciosamente.
Eu adotaria:
Snapshots possuem outro ciclo de vida e não deveriam compartilhar as mesmas garantias de releases.
Para avançar entre estágios, eu evitaria reconstruir um pacote.
O ideal é promover o mesmo artefato validado:
snapshot aprovado
→ release
ou:
quarantine
→ trusted
A identidade do binário precisa permanecer estável.
Repositórios acumulam:
Políticas de limpeza ajudam a controlar retenção e custos.
Exemplos:
As políticas precisam considerar recuperação e auditoria.
O cache remoto é uma otimização e uma camada de controle.
Eu não o trataria como única estratégia de preservação de dependências críticas.
Para componentes essenciais, avaliaria:
Em um cache miss, o Remote Repository depende do upstream.
Uma indisponibilidade pode afetar um build que solicita uma versão nunca armazenada.
Uma estratégia de resiliência pode incluir:
Arquivos como maven-metadata.xml podem ser atualizados.
O comportamento de cache de metadados é diferente do comportamento de artefatos versionados.
Isso importa especialmente para:
Para builds reprodutíveis, eu evitaria depender de ranges como:
<version>[1.0,2.0)</version>
O virtual só reduz dependency confusion se os clientes realmente o utilizarem.
Se uma aplicação ainda puder consultar diretamente o Maven Central, a política central pode ser ignorada.
Eu controlaria:
settings.xml;pom.xml;Reservaria coordenadas internas previsíveis:
com.iisatech.*
org.amar.*
br.com.rmcare.*
Além disso:
Uma organização pode distribuir um Parent POM que centraliza:
Esse parent também se torna um artefato crítico da supply chain.
Precisa de:
O Maven diferencia dependências de aplicação e plugins de build.
A arquitetura precisa considerar:
<pluginRepositories>
Um build pode continuar consultando origens externas por meio de plugins, mesmo que <repositories> esteja controlado.
Política de dependências precisa abranger os dois caminhos.
No Cloud Build, GitHub Actions ou outro sistema, eu não usaria credenciais pessoais.
Usaria identidade de workload:
Artifact Registry usa criptografia gerenciada pelo Google por padrão.
Ambientes com requisitos específicos podem usar CMEK.
Nesse caso, o desenho precisa considerar:
A região deve ser escolhida considerando:
O modo e a localização não podem ser alterados depois da criação.
Migrar exige criar outro repositório e mover os artefatos.
Eu monitoraria:
Cloud Audit Logs ajuda a investigar operações administrativas e de acesso conforme a configuração aplicável.
Gerenciar origem e precedência não significa que o pacote é seguro.
Ainda precisamos de:
O Virtual Repository decide de onde resolver.
Ele não decide sozinho se o pacote deveria ser usado.
Para pacotes sensíveis, eu conectaria:
build confiável
→ testes
→ SBOM
→ provenance
→ publicação no Standard Repository
→ consumo controlado
Isso permite verificar não apenas a origem do repositório, mas também como o artefato foi produzido.
Maven não possui lockfile nativo equivalente a alguns ecossistemas.
Por isso, eu reforçaria:
dependencyManagement;A extensão de autenticação também faz parte da supply chain.
Eu evitaria usar uma versão flutuante.
Também avaliaria:
Uma plataforma compartilhada de pacotes poderia armazenar:
Estrutura possível:
platform-java-releases
platform-java-snapshots
maven-central-cache
platform-maven
As aplicações consumiriam apenas:
platform-maven
Em uma plataforma institucional, pacotes internos poderiam representar:
O virtual reduziria diferenças de configuração entre módulos e equipes.
Em uma plataforma de saúde, eu usaria o registry para controlar:
Além de acesso restrito, seria necessário inventário, scanning e processos de atualização.
| Capacidade | Google Cloud | AWS |
|---|---|---|
| Pacotes privados | Artifact Registry Standard | AWS CodeArtifact |
| Proxy de Maven Central | Artifact Registry Remote | CodeArtifact upstream externo |
| Agregação de origens | Artifact Registry Virtual | Domínio/repositórios com upstreams |
| IAM | Google Cloud IAM | AWS IAM |
| Criptografia gerenciada | Google-managed encryption / CMEK | AWS KMS |
| Publicação Maven | Maven Wagon + mvn deploy |
Maven settings + CodeArtifact auth |
| Cache de terceiros | Remote Repository | External connection/upstream |
| Auditoria | Cloud Audit Logs | CloudTrail |
Os modelos não são idênticos, mas resolvem problemas arquiteturais semelhantes.
Uma arquitetura real precisa considerar:
O cache reduz downloads externos repetidos, mas também acumula conteúdo.
Cleanup policies e visibilidade de uso são necessárias para evitar crescimento indefinido.
O nome menciona containers, mas as tarefas são focadas em repositórios Maven.
A conexão com containers aparece quando esses pacotes alimentam builds de aplicações e imagens.
pom.xml sensível à estruturaAs seções precisam ficar dentro de <project>.
XML inválido impede o Maven de carregar o projeto.
Cada entrada em <repositories> precisa de um id coerente.
O laboratório altera o repositório remoto para:
<id>central</id>
Isso substitui a origem central configurada pelo Super POM do Maven.
Se ~/.m2/repository já possui as dependências, Maven pode não acessar o Remote Repository.
Por isso o laboratório remove o cache local.
Parent POMs podem ser resolvidos antes de extensões declaradas apenas no pom.xml.
O arquivo .mvn/extensions.xml evita esse problema.
O laboratório exclui e recria o cache remoto para demonstrar que o acesso pelo Virtual Repository volta a preenchê-lo.
Quanto maior o número, maior a prioridade do upstream na política do virtual.
Executar mvn deploy novamente com a mesma versão de release pode falhar dependendo da política e do estado do repositório.
Versionamento faz parte da operação.
O laboratório não aprofunda:
Esses pontos transformam uma demonstração em uma plataforma de dependências.
Antes de criar os repositórios, eu responderia:
Quais pacotes são privados?
Quem é o owner e quem pode publicar?
Snapshots e releases devem ser separados?
Quais políticas de retenção e imutabilidade cada um terá?
Quais upstreams externos são permitidos?
Maven Central, mirrors internos ou registries de parceiros?
Qual precedência será aplicada?
Como impedir dependency confusion?
Os clientes podem acessar a internet diretamente?
Como evitar bypass do virtual?
Qual é a identidade dos publishers?
Desenvolvedor, Cloud Build ou pipeline de release?
Como os artefatos são promovidos?
O mesmo binário chega a produção?
Como dependências vulneráveis são bloqueadas?
Qual ferramenta e qual política?
Como responder a indisponibilidade externa?
Cache, mirror, prefetch ou vendor?
Como medir o serviço?
Cache hit, falhas, downloads, armazenamento e pacotes sem uso.
A topologia de repositórios deve refletir o modelo de confiança da organização.
Principais conceitos utilizados neste artigo.
Serviço gerenciado do Google Cloud para armazenar e distribuir artefatos de software.
Repositório que recebe uploads e publicações diretas de artefatos privados.
Proxy com cache para uma origem upstream externa ou outro repositório compatível.
Ponto único de acesso que combina múltiplos upstream repositories.
Repositório consultado por um Remote ou Virtual Repository.
Repositório público amplamente utilizado pelo ecossistema Java.
Camada de transporte usada pelo Maven para interagir com diferentes protocolos e repositórios.
Seção do POM que define destinos de publicação.
Seção do POM que define origens para resolução de dependências.
POM herdado que centraliza configurações e dependency management.
Ataque em que um pacote público conflitante é escolhido no lugar de uma dependência privada esperada.
Quando o artefato solicitado já está armazenado no Remote Repository.
Quando o Remote Repository precisa consultar o upstream.
Versão de desenvolvimento que pode evoluir durante o ciclo.
Versão publicada como artefato estável e identificável.
Evidência verificável sobre como e onde um artefato foi produzido.
.mvn/extensions.xml é importante para resolução antecipada.O laboratório reforça conhecimentos importantes para Cloud, DevOps, DevSecOps e Platform Engineering:
Para Associate Cloud Engineer, reforça criação e operação de recursos, IAM e uso da CLI.
Para Professional Cloud DevOps Engineer e Professional Cloud Security Engineer, aproxima o aprendizado de software supply chain, platform engineering e controles de entrega.
O laboratório começou com um repositório vazio e terminou com uma arquitetura de dependências em três camadas.
Construímos:
Standard
→ pacotes privados
Remote
→ cache controlado do Maven Central
Virtual
→ uma interface única com prioridade
O aprendizado principal não foi apenas executar:
mvn deploy
Foi entender que o local de onde uma dependência é resolvida faz parte do modelo de segurança.
Ao colocar o pacote privado com prioridade maior que o repositório público, a arquitetura reduz risco de confusão de dependências.
Ao adicionar um cache remoto, reduz dependência direta e repetitiva do Maven Central.
Ao expor uma única URL virtual, simplifica a configuração e centraliza a política.
O resultado é um modelo mais próximo de uma plataforma interna de artefatos:
publicar de forma controlada, consumir por uma interface governada e tratar dependências como componentes críticos da supply chain.
No próximo capítulo da Google Cloud Master Journey 2026, a jornada continuará conectando:
Depois de proteger build, artefato e dependência, o próximo passo é unir essas camadas em uma política completa de entrega segura.
As respostas da comunidade chegam em breve.