Configurar Trusted Publishing para publicação segura e automatizada via GitHub Actions#
Nas lições anteriores de empacotamento Python, você aprendeu:
Objetivos de Aprendizagem
Nesta lição, você aprenderá como:
Automatizar o build e a publicação do pacote no GitHub Actions
Configurar Trusted publishing para o projeto
Proteger seu workflow usando hashes e versões de GitHub actions no arquivo de workflow
Este tutorial assume que seu projeto está hospedado no GitHub e que você quer publicar um pacote do seu projeto no PyPI.
Configurar um job de release no GitHub Actions#
O GitHub Actions[1] é uma infraestrutura fornecida pelo GitHub para automatizar workflows de software, diretamente do repositório GitHub do projeto. Você pode configurar testes automatizados para cada pull request, automatizar a publicação de documentação, automatizar a criação de páginas web para o projeto e até automatizar o processo de release. Para esta lição, focaremos no uso de actions para fazer release e publicar seu pacote Python com segurança no PyPI.
Por que o Trusted Publishing é importante
Se você está se perguntando por que o trusted publishing é tão importante, confira este post do blog:que aprofunda o que pode acontecer quando você não protege seus workflows de publicação.
Passo 0: Criar um workflow de release#
Para começar, crie um arquivo chamado release.yaml no diretório .github/workflows do seu projeto. Se o diretório .github/workflows não existir, você pode criá-lo. É convenção do GitHub que todas as GitHub Actions sejam configuradas via arquivos YAML no diretório .github/workflows.
Nomeando seu arquivo de workflow
Você pode dar ao arquivo de workflow o nome que quiser. Sugerimos usar algo simples e expressivo como release.yaml, para que você, seu eu futuro e colaboradores que trabalham no seu projeto saibam exatamente o que o workflow faz.
Passo 1: Nomear o workflow#
No topo do arquivo release.yaml, digite o seguinte:
name: Release
Isso fornece um nome ao workflow que você pode usar para encontrar rapidamente todas as execuções desta GitHub Action na aba «Actions» do repositório GitHub.
Esta imagem mostra um exemplo de workflow configurado para o release. No topo, na caixa vermelha rotulada «1», você vê a aba «Actions» do repositório GitHub. À esquerda, na caixa vermelha rotulada «2», você pode ver o nome do workflow, conforme configurado neste passo. Por fim, no centro, na caixa vermelha rotulada «3», você pode ver várias execuções do workflow, para os releases «1.0» e «1.0.1» do pacote.#
Passo 2: Adicionar triggers ao workflow#
Todo workflow do GitHub Actions é executado quando certas condiçõessão atendidas. Neste caso, assumimos que um workflow de release só deve ser executado quando o proprietário do repositório cria um novo releasepara o pacote. Adicione o seguinte ao arquivo release.yaml para garantir que ele seja executado quando você criar e publicar um release:
on:
release:
types:
- published # This tells GitHub to only run this workflow when a release of your package is published
Passo 3: Configurar os jobs no workflow#
Um arquivo de workflow do GitHub Actions pode conter vários jobs que são executados de forma independente; cada job também pode ter vários steps. Quando acionado, a GitHub Action executa todos os jobs em um workflow (excluindo quaisquer steps que tenham requisitos condicionais).
Nota
Jobs e steps também podem ter lógica condicionalque permite que sejam executados apenas se critérios específicos existirem. Por exemplo, você pode querer que um step do job publique no PyPI apenas se um release foi feito para o pacote. Mas você pode querer testar o build do pacote toda vez que fizer merge de um novo pull request.
Para um job de release, você precisa clonar ou fazer checkout do repositório. Você pode usar a action actions/checkout para fazer checkout do código. Em seguida, instala e usa o Hatch para fazer o build do seu pacote.
Você também precisa garantir que o Hatch esteja configurado na máquina que o GitHub está usando para executar o workflow.
Uma definição mínima de job seria assim:
jobs:
build_package:
name: Build the package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.12" # Select the version that you want to build your package on
- name: Upgrade pip, install Hatch, and check Hatch version
run: |
pip install --upgrade pip
pip install --upgrade hatch
hatch --version # Verify that Hatch is installed
Observe que acima você fornece uma versão para cada step de action. action/checkout@v5 diz ao GitHub para usar a versão 5 da action checkout. A action checkout faz checkout do código do seu repositório. Neste caso, o código será usado para fazer o build do seu pacote.
Em seguida, você aprenderá sobre uma maneira melhor de proteger (ou «endurecer») seu workflow
Passo 4: Proteger o workflow do GitHub Actions#
Há várias melhorias que você pode fazer no workflow do GitHub Actions que acabou de configurar para melhorar a segurança e a legibilidade.
Primeiro, podemos dar nomes aos passos relevantes do processo para aumentar a legibilidade dos logs gerados durante a execução do workflow. Isso pode ser feito usando linhas name: <sua descrição aqui>.
Mais importante, toda vez que você usar uma action existente (via uses), deve fixar essa action a um commit hash. Fixar sua action garante que, se um usuário malicioso assumir o controle da action, ele não conseguirá impactar seu repositório (um exemplo de ataque à cadeia de suprimentos devido a GitHub Actions é o recente ataque tj-actions/changed-files[2]).
Dica
Habilitar o Dependabot[3] no repositório garantirá que suas actions permaneçam atualizadas. A ferramenta dependabot abrirá pull requests que atualizam as versões das suas actions na frequência que você desejar.
Assim, o workflow que você deve usar deve ser semelhante a:
name: Release
on:
release:
types:
- published
permissions: {} # no permissions to the token at global level
jobs:
build_package:
name: Build the package
runs-on: ubuntu-latest
permissions:
contents: read # this job only needs read access
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12" # Select the version that you want to build your package on
- name: Upgrade pip, install Hatch, and check Hatch version
run: |
pip install --upgrade pip
pip install --upgrade hatch
hatch --version # Verify that Hatch is installed
- name: Build artifacts
Agora, você pode fazer commit do arquivo .github/workflows/release.yaml no repositório e fazer push para o GitHub.
Neste ponto, se você criar um novo release para seu projeto no GitHub, o workflow configurado deve ser executado e fazer o build de uma wheel para você. Infelizmente, a wheel só está disponível no runner e será excluída ao final da execução do workflow.
Passo 5: Fazer upload do artefato build para GitHub Artifacts#
Você precisa adicionar mais um step à definição do job para poder acessar a wheel. Você fará upload dela para a área temporária de artifacts[4]. Adicione o seguinte ao arquivo release.yaml:
with:
path: dist/
name: dist.zip
if-no-files-found: error
retention-days: 1
Parâmetros de upload de artifacts
Acima, você configurou o artifact para ser excluído após 1 dia. O armazenamento de artifacts no GitHub Actions é temporário; os usuários não devem baixar seu pacote dos artifacts do GitHub.
Você também configurou o job de release para gerar erro se o diretóriodist/ não existir. Isso significa que hatch build (do step anterior) falhou ao fazer o build do nosso pacote, então não há nada para fazer release.
Neste ponto, se você fizer push do release.yaml para o GitHub e criar um novo release, o job do GitHub Actions irá:
executar,
clonar seu repositório,
instalar e configurar o Hatch,
fazer o build do seu pacote e
fazer upload do seu pacote como um arquivo para o armazenamento de artifacts.
Esta figura mostra um exemplo de workflow de release que acabou de terminar de executar. Cada step no log corresponde a um step na definição do workflow.#
Na parte inferior da página de execução do workflow no GitHub, você deve ver uma seção para os artifacts produzidos durante a execução e enviados para esta área de armazenamento:
Esta figura mostra o artifact produzido pelo workflow de release acima. Agora está marcado como expirado, pois o workflow foi executado há mais de um dia.#
Você pode baixar o artifact (antes que expire), descompactá-lo e instalar a wheel contida nele. No entanto, isso só deve ser feito se você quiser testar a wheel build. Em seguida, você configurará o upload para o PyPI usando trusted publishing.
Configurar publicação automática no PyPI#
O job que você configurou acima usando GitHub Actions faz o build do seu pacote usando seu código. Você ainda precisa fazer upload dele para o PyPI. Você poderia fazer upload do pacote no mesmo job, mas é melhor criar um separado para manter a separação de tarefas. É por isso que, na seção anterior, fizemos upload do artifact para o armazenamento temporário.
No novo job, você fará download do pacote de lá e fará upload dele para o PyPI. Como o job build não faz mais nada, não há possibilidade de o pacote ser comprometido antes do release.
Passo 1: Adicionar o job de upload#
No arquivo release.yaml, adicione o seguinte novo job, após o job definido na seção anterior:
name: Publish release to PyPI
needs: [build_package]
runs-on: ubuntu-latest
environment:
name: pypi
url: <URL TO YOUR PROJECT HERE>
permissions:
contents: read # this job needs read access
id-token: write # but also needs to be able to write the publishing token
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist.zip
path: dist/
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
Certifique-se de alterar a URL
Lembre-se de alterar o valor url: para a URL do seu pacote no PyPI!
Este job tem dois steps:
Ele usa
download-artifactpara baixar os artifacts build no job anteriorEle usa
gh-action-pypi-publishpara publicar o pacote no PyPI.
Você está quase lá!! Agora, você só precisa habilitar o trusted publishing para seu projeto no PyPI. E então, seu trabalho está feito!
Passo 2: Habilitar trusted publishing no PyPI#
Esta lição é a primeira de uma série de lições para ajudar você a começar com o empacotamento em Python.#
Antes da criação do trusted publishing, para fazer upload para o PyPI a partir de GitHub Actions você precisaria adicionar o nome de usuário e a senha como argumentos ao step gh-action-pypi-publish. Embora a documentação recomende usar o environment secrets do GitHub para a senha/token, em vários casos os usuários colavam a senha diretamente no arquivo de workflow. Além disso, o vazamento acidental da senha ou token poderia permitir que atacantes publicassem novos pacotes usando sua conta, até que você descobrisse o comprometimento e revogasse as credenciais vazadas.
Para prevenir esses incidentes e melhorar a segurança da cadeia de suprimentos, desenvolvedores criaram o Trusted Publishing. O Trusted Publishing permite registrar um workflow de publicação no PyPI e mapear esse workflow a um workflow de automação (por exemplo, GitHub Actions) que tem permissão para publicar o pacote.
Você não precisa inserir um valor de token ou senha em um workflow de trusted publisher. É uma conexão segura entre seu
Trusted Publishing fora do GitHub Actions
O Trusted Publishing suporta outras plataformas de automação, além do GitHub Actions. Também é possível configurar um trusted publisher para vários workflows ou vários publishers para o mesmo pacote. Esses são usos avançados, fora do escopo desta lição.
Para esta lição, focaremos em configurar um trusted publisher para um projeto que já existe no PyPI. Se você completou a lição sobre publicação no PyPI, você já deve ter este projeto criado.
Esta etapa de configuração precisa ser executada apenas uma vez para o projeto. Releases futuros apenas executarão o workflow do GitHub Actions que estamos configurando em release.yaml.
Na página «Your projects» no PyPI, clique em «Manage» em qualquer projeto que você queira configurar.
Esta imagem mostra vários projetos. O botão «Manage» está destacado para um dos projetos, aquele que queremos configurar o trusted publishing.#
Em seguida, clique em «Publishing» na barra lateral do projeto.
Após clicar no botão «Manage», chegamos à página do projeto. Na barra lateral, temos a opção «publishing», conforme destacado aqui.#
Isso levará você à página de configuração do publisher para o projeto. Trusted publishers podem ser configurados via os formulários aqui. Preencha o formulário do GitHub com as seguintes informações:
Owner: o nome da organização GitHub da organização que possui o projeto. Se este for seu projeto pessoal, use seu nome de usuário GitHub aqui.
Repository name: o nome do repositório que contém o projeto.
Workflow name: Deve ser
release.yamlse você seguiu este guia; é o workflow que acabamos de configurar.Environment name: Deve ser
pypi, pois é o que configuramos emrelease.yaml.
Depois de preencher este formulário e clicar em «Add», o publisher é configurado e pode ser usado para publicar novos releases do seu pacote.
Workflow de release do GitHub Actions totalmente endurecido#
Para maior segurança, também é recomendado controlar as permissões do token GitHub usado em cada job do workflow. As permissões devem ser definidas no nível do job e ser o mais mínimas possível. Um workflow que configura trusted publishing e também faz isso é o seguinte:
name: Release
on:
release:
types:
- published
permissions: {} # no permissions to the token at global level
jobs:
build_package:
name: Build the package
runs-on: ubuntu-latest
permissions:
contents: read # this job only needs read access
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12" # Select the version that you want to build your package on
- name: Upgrade pip, install Hatch, and check Hatch version
run: |
pip install --upgrade pip
pip install --upgrade hatch
hatch --version # Verify that Hatch is installed
- name: Build artifacts
run: hatch build
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: dist/
name: dist.zip
if-no-files-found: error
retention-days: 1
publish_release_to_pypi:
name: Publish release to PyPI
needs: [build_package]
runs-on: ubuntu-latest
environment:
name: pypi
url: <URL TO YOUR PROJECT HERE>
permissions:
contents: read # this job needs read access
id-token: write # but also needs to be able to write the publishing token
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist.zip
path: dist/
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
Você pode copiar o conteúdo acima para seu arquivo release.yaml. Você só precisa atualizar o campo url: e configurar o trusted publishing no PyPI.
Nota
O workflow acima deve estar atualizado com as versões atuais das GitHub actions. No entanto, é bom habilitar o Dependabot para atualizar as versões das actions no futuro.
Você habilitou o trusted publishing para seu projeto#
Parabéns!! Você agora configurou seu projeto para fazer releases seguros quando uma nova versão for marcada no GitHub. O workflow que configuramos faz o build do pacote a partir da versão exata do código que estamos marcando. Isso oferece uma garantia para seus usuários de que o pacote que você fez release faz exatamente o que o código declara que faz. Há pouca ou nenhuma possibilidade de vulnerabilidades relacionadas à cadeia de suprimentos surgirem do seu pacote! Se você tem um pacote pronto para uso no mundo real no PyPI real, pode seguiros mesmos passos para publicá-lo com segurança.