Configurar Trusted Publishing para publicação segura e automatizada via GitHub Actions#

Nas lições anteriores de empacotamento Python, você aprendeu:

  1. Como criar um pacote Python

  2. Como publicar o código no PyPI e no Conda

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.

Gráfico mostrando 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, "Release", 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.

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.

Gráfico mostrando 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.

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:

Gráfico mostrando um exemplo de artifact produzido pelo workflow de release.

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-artifact para baixar os artifacts build no job anterior

  • Ele usa gh-action-pypi-publish para 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#

Diagrama mostrando o workflow de trusted publisher do PyPI: o Passo 1 faz build dos arquivos de distribuição via GitHub, o Passo 2 usa um environment confiável (PyPI), o Passo 3 faz upload seguro para o PyPI. Mostra a cadeia de confiança com ícone de cadeado conectando a GitHub Action ao Python pacote Index.

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.

Gráfico mostrando uma captura de tela da página "Your projects" no PyPI. O botão "Manage" de um dos projetos está destacado.

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.

Gráfico mostrando a página de gerenciamento de um projeto. O link "Publishing" na barra lateral está destacado.

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.yaml se você seguiu este guia; é o workflow que acabamos de configurar.

  • Environment name: Deve ser pypi, pois é o que configuramos em release.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.

Notas de rodapé#