Skip to main content

Use o GitHub Actions para publicar pacotes npm com segurança

Escrito por

10 de novembro de 2020

0 minutos de leitura

O GitHub Actions vem ganhando popularidade desde que o GitHub anunciou sua disponibilidade geral para todos os desenvolvedores e repositórios na plataforma. Impulsionada por alguns limites de uso que estamos vendo no ecossistema — como os novos limites de cobrança e de uso para código aberto do Travis CI —, essa tendência deve levar ainda mais desenvolvedores a migrar suas automações de software para o GitHub Actions.

Neste artigo, quero mostrar como estou usando o GitHub Actions para publicar pacotes npm que mantenho em meus projetos de código aberto. Se você segue o fluxo do GitHub, que consiste em fluxos de trabalho com pull requests do GitHub, isso também vai unificar ainda mais a experiência das suas equipes e dos colaboradores da comunidade em torno dos fluxos de trabalho do GitHub no projeto.

O que é o GitHub Actions?

O GitHub Actions é uma tecnologia desenvolvida pelo GitHub para oferecer aos desenvolvedores uma maneira de automatizar seus fluxos de trabalho de integração contínua — ajudando a compilar, implantar, agendar tarefas recorrentes e muito mais. Disponível nativamente e integrado aos repositórios do GitHub, o GitHub Actions oferece muitos fluxos de trabalho reutilizáveis criados por colaboradores da comunidade, como publicar pacotes npm, publicar imagens Docker, executar testes de segurança e muito mais.

Como as Actions funcionam no GitHub?

A infraestrutura em nuvem do GitHub executa o GitHub Actions para os usuários. Para isso, basta criar um arquivo de fluxo de trabalho no diretório .github/workflows de um repositório do GitHub, descrevendo o gatilho, o agendamento do trabalho e as ações que ele deve executar usando YAML.

O que é um fluxo de trabalho do GitHub?

Um fluxo de trabalho do GitHub é um conjunto de tarefas executadas com base em um gatilho ou em um agendamento no formato cron. Uma tarefa é composta por uma ou mais etapas que formam um fluxo de trabalho automatizado.

Como configurar um projeto Node.js para o GitHub Actions

Vamos adicionar uma automação inicial do GitHub Actions a um projeto Node.js. A tarefa do GitHub Actions vai instalar todos os pacotes npm necessários, executar testes e, por fim, publicar nosso projeto como um pacote npm que os usuários poderão usar.

Nosso pacote npm será uma interface de linha de comando (CLI) para você explorar a incrível lista de palestras da SnykCon 2020 — o primeiro evento global de segurança da Snyk, realizado em 2020.

O projeto completo está hospedado no GitHub. Veja uma prévia de como é o pacote npm:

Imagem no estilo de um terminal com a lista das palestras da SnykCon 2020, incluindo sessões sobre DevSecOps e segurança da superfície de ataque de front-end.

Para iniciar um fluxo de trabalho completo de CI do GitHub, crie o seguinte arquivo do GitHub Actions na raiz do repositório: .github/workflows/main.yml

name: main

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [12.x, 14.x]
    steps:
      - uses: actions/checkout@v2
      - name: Build on Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci --ignore-scripts
      - run: npm run build --if-present
        name: Build
      - run: npm test
        env:
          CI: true

O exemplo acima é basicamente um modelo padrão para compilar e testar projetos Node.js. Ele faz o seguinte:

  1. O gatilho é acionado a cada push, em todas as branches.

  2. Executamos essa Action nas versões 12.x e 14.x do Node.js para garantir a compatibilidade com as duas versões LTS do Node.js.

  3. Em seguida, a tarefa executa várias etapas: faz checkout do código-fonte no Git, realiza uma instalação segura e determinística do npm, executa uma etapa de compilação, se houver, e, por fim, roda os testes.

Ao enviar novos commits para o repositório, seja seguindo um fluxo do GitHub ou um fluxo de trabalho com pull requests do GitHub, você verá novas tarefas sendo enfileiradas na aba Actions do repositório. Veja a seguir os testes em execução para o pacote npm snykcon:

Fluxo de trabalho do GitHub Actions para o repositório snykcon, mostrando uma compilação bem-sucedida com Node.js 12.x e a execução dos testes npm.

Como publicar pacotes npm com o GitHub Actions

Se todos os testes forem aprovados e esse fluxo de automação for executado na branch principal, poderemos automatizar o lançamento de novas versões!

Ao lançar pacotes, é importante considerar as implicações de segurança dessas ações. Vamos analisar algumas delas:

  1. Código malicioso: se alguém inserir intencionalmente uma vulnerabilidade como parte de uma contribuição de código e você não perceber, um lançamento automático ao mesclar com a branch principal fará com que ela fique imediatamente disponível para os usuários. Se você fizer o lançamento manualmente logo em seguida, o resultado será o mesmo. No entanto, quanto mais tempo houver entre a mesclagem de pull requests e os lançamentos, mais tempo a comunidade terá para analisar e ajudar a identificar esses problemas.

  2. Dependência vulnerável: se alguém mal-intencionado adicionar uma dependência com uma vulnerabilidade pública conhecida, ela chegará aos seus usuários quando for baixada durante a instalação. Usar uma ferramenta gratuita como a Snyk para conectar aos seus repositórios Git e adicionar uma verificação de status que impeça o lançamento de versões vulneráveis das dependências resolve exatamente esse problema.

  3. Dependência maliciosa por meio de um lockfile: você já pensou no que aconteceria se uma contribuição para atualizar as dependências de package.json injetasse um pacote malicioso no lockfile, que ninguém costuma revisar, certo? Escrevi sobre por que os lockfiles do npm podem ser um ponto cego de segurança para a injeção de módulos maliciosos. Portanto, confira esse conteúdo em detalhes se ainda não tinha considerado esse vetor de ataque.

  4. Roubo do seu token npm: no passado, várias tentativas envolvendo pacotes maliciosos tinham como objetivo roubar o token npm de alguém ou outras informações confidenciais armazenadas em variáveis de ambiente.

A lista acima não pretende cobrir todas as preocupações de segurança, mas certamente destaca várias delas que devemos conhecer.

Falando em segurança, você achou interessante e instrutiva essa lista de possíveis preocupações? É um exercício rápido de modelagem de ameaças, que você pode praticar com mais frequência ao desenvolver funcionalidades. Escrevemos sobre isso em nosso processo de DevSecOps, e Alyssa Miller fez uma ótima palestra na SnykCon sobre modelagem de ameaças com histórias de usuário: o jeito DevSecOps. Confira!

Vamos voltar à lista e nos concentrar na última preocupação de segurança mencionada: o roubo do seu token npm. Como este artigo trata da publicação de pacotes npm, precisamos disponibilizar um token npm para o fluxo de trabalho do GitHub Actions. Historicamente, isso não era recomendado pelos seguintes motivos:

  • Recursos do npm: historicamente, para publicar pacotes npm usando um token npm, era necessário desativar a autenticação de dois fatores na sua conta npm. Isso não é mais necessário, e vamos aprender como fazer!

  • Roubo de token durante a instalação de pacotes maliciosos: se você disponibilizar o token npm como variável de ambiente no CI, pacotes maliciosos presentes na árvore de dependências do seu pacote — além das dependências diretas — poderão acessá-lo, por exemplo, durante a instalação do npm, que, por padrão, permite que os pacotes executem qualquer comando.

Então, como lidar com essas duas preocupações legítimas de segurança?

Primeiro, o npm passou a permitir a autenticação de dois fatores junto com a emissão de tokens de automação, então esses dois recursos não são mais incompatíveis. Segundo, o GitHub Actions permite disponibilizar variáveis de ambiente apenas para uma etapa específica de uma tarefa. Assim, podemos disponibilizá-las somente para o comando npm publish, e não para o npm install, que também daria acesso a elas às dependências indiretas.

Vamos começar.

Primeiro, ative a autenticação de dois fatores na sua conta npm. Acesse as configurações da sua conta em https://npmjs.com/ e ative o modo 2FA para autorização e publicação.

Tela de configuração da autenticação de dois fatores com as opções Autorização, Autorização e publicação ou Desativar, com Autorização e publicação selecionada

Você precisará vincular um dispositivo autenticador, como o app Google Authenticator no celular ou o 1Password, caso use esse gerenciador de senhas.

Em seguida, acesse o gerenciamento de tokens de acesso no npm e crie um novo token.

Painel de tokens de acesso com pesquisa de pacotes e controles para gerar um novo token e excluir os tokens selecionados

Crie um token do tipo Automation, como mostra a captura de tela abaixo. Conforme indicado na descrição, ele ignora a autenticação de dois fatores e permite o uso em fluxos de trabalho de integração contínua (CI):

Formulário de novo token de acesso do npm, com Automação selecionada entre os tipos somente leitura, automação e publicação

Em seguida, disponibilize esse token no GitHub Actions criando primeiro um segredo no gerenciamento de segredos do repositório do GitHub, assim:

Configurações de secrets do repositório no GitHub, mostrando um secret NPM_TOKEN criptografado e os botões Atualizar e Remover

Por fim, atualize o fluxo de trabalho do GitHub Actions para incluir uma etapa de lançamento. Depois da tarefa build, adicione a seguinte tarefa publish:

publish:
    if: github.ref == 'refs/heads/main'
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 14
      - name: Publish
        run: |
          npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
          npm publish --ignore-scripts
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

É fundamental observar o argumento --ignore-scripts no comando npm publish, essencial para um fluxo de publicação seguro. Ele instrui o comando de publicação da CLI do npm a ignorar todos os scripts de ciclo de vida definidos no manifesto packge.json. Isso é importante: quando um pacote malicioso é instalado como parte do grafo de dependências, ele pode adicionar ao seu pacote uma entrada como prepack: “echo ‘do something malicious’”, que será executada quando você rodar npm publish. Como você pode imaginar, essa entrada maliciosa pode causar muito mais do que simplesmente exibir algo na tela.

O fluxo de trabalho que estamos usando reduz bastante o risco de roubo dos tokens npm ou de outros ataques que executam comandos arbitrários, porque:

  1. Na tarefa build, instalamos pacotes sem permitir que executem comandos arbitrários, graças ao argumento de comando --ignore-scripts passado para npm ci.

  2. Nossa tarefa publish começa com um novo checkout do repositório. Isso significa que, mesmo que fosse necessário permitir a execução de scripts durante a instalação dos pacotes npm na etapa build anterior, todas as tentativas maliciosas de injetar dados no arquivo package.json do pacote seriam inúteis.

  3. Como precaução, a etapa publish inclui o argumento de comando --ignore-scripts para evitar a execução de scripts de ciclo de vida do npm durante essa fase.

  4. O token de automação do npm para publicar um pacote só fica disponível na etapa de publicação.

Considerando tudo isso, você provavelmente dormiria mais tranquilo se exigisse autenticação de dois fatores para publicar cada versão do pacote. No entanto, habilitar esse recurso em um ambiente de CI exige uma configuração mais elaborada, então não vamos abordá-la neste artigo.

Essa tarefa é executada somente na branch principal — assim, não fazemos lançamentos durante a execução dos testes de um pull request — e usa apenas uma versão específica do Node.js, em vez de executar tarefas em paralelo com versões diferentes.

Quando um pull request do GitHub é mesclado ou um commit é enviado para a branch principal, a tarefa publish a seguir será executada e lançará o pacote npm.

Fluxo do GitHub Actions mostrando uma tarefa de publicação bem-sucedida, com as etapas de configuração, checkout, Node.js, publicação e conclusão finalizadas

Consulte o arquivo do fluxo de trabalho completo do GitHub Actions como referência.

É importante observar que não abordamos nenhum tipo de versionamento semântico automatizado para atualizar automaticamente as versões dos nossos pacotes npm, de acordo com o tipo de alteração no código: patch, minor ou major. Para isso, recomendo avaliar o semantic-release, que o Snyk Advisor também indica como um pacote muito bem mantido:

Painel de integridade do pacote semantic-release no Snyk, com pontuação de 90/100, tendências de downloads e gráficos de atividade de manutenção.

Onde o GitHub Actions é executado?

O GitHub Actions funciona no escopo de um repositório específico e é executado e gerenciado pelos serviços de infraestrutura em nuvem do GitHub, com suporte a executores nas plataformas Mac, Windows e Linux. Também é possível usar executores do GitHub hospedados por você, por exemplo, na infraestrutura do Google Cloud.

O que é o fluxo do GitHub?

O GitHub Actions funciona no escopo de um repositório específico e é executado e gerenciado pelos serviços de infraestrutura em nuvem do GitHub, com suporte a executores nas plataformas Mac, Windows e Linux. Também é possível usar executores do GitHub hospedados por você, por exemplo, na infraestrutura do Google Cloud.

Em resumo

Em resumo, é possível automatizar com segurança a criação de pacotes npm e a publicação deles no ecossistema npm usando o GitHub Actions. A vantagem de escolher o GitHub Actions é manter toda a experiência de desenvolvimento de um fluxo de trabalho Git dentro da plataforma GitHub.

Também recomendo conhecer outras integrações do GitHub Actions que você pode adicionar facilmente ao seu projeto:

Experimente minha is-website-vulnerable GitHub Action se quiser acompanhar testes de segurança de ponta a ponta em um site (detectando bibliotecas JavaScript vulneráveis).

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.