Use o GitHub Actions para publicar pacotes npm com segurança
10 de novembro de 2020
0 minutos de leituraO 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:

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
O exemplo acima é basicamente um modelo padrão para compilar e testar projetos Node.js. Ele faz o seguinte:
O gatilho é acionado a cada push, em todas as branches.
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.
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:

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:
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.
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.
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.
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.

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.

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):

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

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:
É 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:
Na tarefa build, instalamos pacotes sem permitir que executem comandos arbitrários, graças ao argumento de comando
--ignore-scriptspassado paranpm ci.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.jsondo pacote seriam inúteis.Como precaução, a etapa publish inclui o argumento de comando
--ignore-scriptspara evitar a execução de scripts de ciclo de vida do npm durante essa fase.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.

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:

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:
Conecte a Snyk ao seu repositório Git para receber pull requests com correções automatizadas.
Você pode usar o Snyk GitHub Actions para testar as dependências do seu pacote npm e até mesmo as imagens dos seus contêineres Docker. Repositório oficial: https://github.com/snyk/actions.
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.