Permissões e Segurança
Segurança
Script injection é sobre alguma sequência de comandos que está sendo injetada no workflow de fora que pode ser usada para coisas maliciosas.
Vamos imaginar que um workflow fosse disparado sempre que uma nova issue acontecesse e o título da issue fosse utilizado no workflow de alguma maneira. Dependendo onde injetamos este valor e como injetamos, se o título fosse uma string representando um comando seria executado.
Sempre verifique actions de terceiros e a reputação dos mesmos. Reaproveitamento de código é algo bom e ruim ao mesmo tempo. Uma vez que actions executa comandos, esses comandos podem roubar suas credenciais.
Cuide da permissão de acesso ao repositório e evite permissões excessivamente permissivas. Quando criar um token para alguma coisa tenha certeza que somente tenha a permissão de fazer o que é preciso e nada além disso. Em um fluxo de trabalho que faça somente testes seria mais interessante que o workflow somente tenha permissão de leitura.
Vamos analisar esse workflow.
name: Label Issue
on:
issues:
types:
- opened
jobs:
assign-label:
runs-on: ubuntu-latest
steps:
- name: Assign label
run: |
issue_title="${{ github.event.issue.title }}"
if [[ "$issue_title" == *"bug"* ]] then
echo "Issue is about a bug!"
else
echo "Issue is not a bug
fi"
Se tiver bug no nome então vai imprimir que é um bug senão vai imprimir que não é.
Se abrirmos uma issue com o título Something's wrong
temos isso.
Mas se abrirmos uma issue com o título a";echo Got your secrets
.
O que aconteceu foi que o a"
fechou o "$
da primeira linha e depois o restante ; echo Got your secrets
foi executado.
O motivo disso ter acontecido é que rodamos esse comando no shell do runner. O nosso código permite isso. Qualquer coisa dentro de $()
dentro do shell é interpretado. O código abaixo não permitiria isso.
name: Label Issue
on:
issues:
types:
- opened
jobs:
assign-label:
runs-on: ubuntu-latest
steps:
- name: Assign label
env:
TITLE: ${{ github.event.issue.title }}
run: |
if [[ "$TITLE" == *"bug"* ]] then
echo "Issue is about a bug!"
else
echo "Issue is not a bug
fi"
Uma das maneiras de se defender e evitar o uso de interpolações dentro do código no run e sempre que possível utilizar actions ao invés do run.
Sobre actions primeiro procure actions desenvolvida pelo time do github, depois de parceiros verificados e somente em último caso as públicas.
Procure verificar o repositório da action e conferir o código.
Permissão
Até agora rodamos os workflows sem nos preocupar com a permissão.
Vamos analisar este workflow. Toda vez que uma issue for criada somente irá rodar se tiver o nome bug na issue. Estamos usando a api github para setar a label bug nesta issue.
name: Set Label Issue
on:
issues:
types:
- opened
jobs:
assign-label:
runs-on: ubuntu-latest
steps:
- name: Assign label
if: contains(github.event.issue.title, 'bug')
run: |
curl -X POST \
--url https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels \
-H 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
-H 'content-type: application/json' \
-d '{
"labels": ["bug"]
}' \
--fail
Esse workflow somente precisa de permissão para interagir com issue mas não precisa de permissão para pull request nem usar o código do repositório.
As permissões são em nível de job não de workflow. Se definirmos uma permissão em nível de workflow todos os jobs herdarão esta permissão, podendo ser redefinidas dentro de cada job.
Estas são as permissões que podemos definir para um github token.
permissions:
actions: read|write|none
attestations: read|write|none
checks: read|write|none
contents: read|write|none
deployments: read|write|none
id-token: read|write|none
issues: read|write|none
discussions: read|write|none
packages: read|write|none
pages: read|write|none
pull-requests: read|write|none
repository-projects: read|write|none
security-events: read|write|none
statuses: read|write|none
Vou colocar direto no exemplo algumas anotações
name: Set Label Issue
on:
issues:
types:
- opened
jobs:
assign-label:
# permissions: write-all # full permission Esta é a permissão default.
# permissions: read-all # full viewer permission
permissions:
issues: write
# issues: read # Vai dar problema com 403
runs-on: ubuntu-latest
steps:
- name: Assign label
if: contains(github.event.issue.title, 'bug')
run: |
curl -X POST \
--url https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels \
-H 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
-H 'content-type: application/json' \
-d '{
"labels": ["bug"]
}' \
--fail
Esta é mais uma camada de proteção que podemos usar, mas ainda sim pode permitir um script injection.
Se a permissão fosse somente de leitura não poderíamos setar uma label com uma permissão negada de código 403.
Toda vez que um job é criado um token é gerado para esse job e podemos acessar via ${{ secrets.GITHUB_TOKEN }}
.
É possível trocar a restrição de permissiva para restritiva para o actions no em settings. Isso garante que a permissão para os workflows precisam ser dadas corretamente. Vale a pena conferir na documentação.
Não sei se é uma boa idéia deixar que github actions crie e aprove pull requests a não ser que um bom gitflow seja utilizado no repositório.
Vale a pena dar uma olhada em pull requests vindo de forks e torná-lo mais restritivos caso esteja trabalhando em um projeto público. O default é que se você aprovar a primeira vez os próximo serão aprovados automaticamente.