Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions .github/workflows/02-tests-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ jobs:
- name: "Checkout do código"
uses: actions/checkout@v4

# INSIRA AQUI A LÓGICA PARA RODAR OS TESTES E VERIFICAR A COBERTURA
###
###
###
- name: "Setup Node"
uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}

- name: "Instalar Deps"
run: npm ci

- name: "Executar tests"
run: npm run tests

- name: "Extrair porcentagem de cobertura" # Esse step será validado pelo desafio, não altere o nome. No final, ele deve gerar o output "coverage" com a porcentagem de cobertura.
id: coverage
Expand All @@ -32,6 +38,12 @@ jobs:
echo "Coverage: $COVERAGE%"
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT

- name: "Valida se os testes passaram"
if: ${{ steps.coverage.outputs.coverage < env.COVERAGE_MIN }}
run: |
echo "Cobertura mínima não atendida: Esperado - ${{ env.COVERAGE_MIN }}%. Atingido: ${{ steps.coverage.outputs.coverage }}% "
exit 1

generate-certificate: # DAQUI PARA BAIXO, NÃO ALTERAR
name: "Desafio Nível 2 - Certificado"
runs-on: ubuntu-latest
Expand Down
70 changes: 70 additions & 0 deletions .github/workflows/03-build-containers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: "Nível 3: Containers e Segurança"

on:
pull_request:
types: [closed]
branches: [ main ]

permissions:
contents: read
packages: write

env:
CHALLENGE_LEVEL: 3
CHALLENGE_NAME: "containers-e-seguranca"
REGISTRY: ghcr.io

jobs:
build-scan-and-push:
name: "Build, Lint, Trivy Scan e Push no GHCR"
if: #????
runs-on: ubuntu-latest

steps:
# AQUI VAI O CÓDIGO DO DESAFIO :)

generate-certificate: # DAQUI PARA BAIXO, NÃO ALTERAR
name: "Desafio Nível 3 - Certificado"
needs: build-scan-and-push
if: success()
runs-on: ubuntu-latest
steps:
- name: "Gerar certificado"
run: |
mkdir -p certificates
cat > certificates/level-3-certificate.md << EOF
# Certificado de Conclusão - Nível 3

**Descomplicando Github Actions - GitHub Actions Edition**
---

Este certificado atesta que **${{ github.actor }}** concluiu com sucesso:
## Nível 3: Containers e Segurança

**Competências desenvolvidas:**
- Build de imagem Docker
- Lint de Dockerfile com Hadolint
- Scan de vulnerabilidades com Trivy (CRITICAL = 0)
- Relatório de vulnerabilidades como artefato
- Smoke test de execução do container
- Publicação no GitHub Container Registry (GHCR) condicionada ao scan
- Boas práticas de supply chain

**Data de conclusão:** $(date)
**Repositório:** ${{ github.repository }}
**Workflow:** ${{ github.run_id }}

---
**Badge conquistado:** Containers e Segurança

---
*Certificado gerado automaticamente pelo GitHub Actions*
*LINUXtips*
EOF

- name: "Upload do certificado"
uses: actions/upload-artifact@v4
with:
name: level-3-certificate
path: certificates/
retention-days: 30
69 changes: 69 additions & 0 deletions DESAFIO-03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Desafio 03 – Containers e Segurança (GHCR)

Opa, beleza? Foi mal estar atrapalhando seu sextou aí, mas a gente tá com um problema aqui na empresa e precisamos da sua ajuda. Seguinte, aquela aplicação que implementamos os pipelines iniciais de setup e os testes agora está sofrendo uma modernização e vai passar a utilizar docker. Com isso, precisamos que você crie pra gente um terceiro pipeline que faça o seguinte:

## Requisitos do pipeline

- Deve existir um novo workflow do GitHub Actions para o Nível 3 (containers e segurança).
- O disparo deve acontecer quando um Pull Request para a branch `main` for fechado com merge (evento `pull_request`, `types: [closed]` + condição `github.event.pull_request.merged == true`).
- O workflow deve:
- Fazer login no GitHub Container Registry (GHCR) utilizando `GITHUB_TOKEN`.
- Montar a tag da imagem sempre com o SHA do commit, e com `owner`, `IMAGE_NAME` e `registry` em minúsculas.
- Executar lint do `Dockerfile` com **Hadolint**, salvando o resultado em `lint-report.txt` e falhando se forem encontrados os problemas **DL3006** ou **DL3008**.
- Buildar a imagem Docker para permitir o scan de vulnerabilidades.
- Executar o scan de vulnerabilidades com **Trivy** na imagem construída.
- O resultado deve ser salvo em um relatório `trivy-report.txt` e publicado como artefato, mesmo que não haja falhas.
- O workflow deve falhar caso o Trivy encontre vulnerabilidades de severidade **CRITICAL**.
- Executar um **smoke test** da imagem rodando `node --version`. Caso não haja saída, o job deve falhar.
- Somente publicar no GHCR se **todas** as validações acima forem aprovadas.
- Gerar o artefato `level-3-certificate.md` (não alterar a seção do certificado, assim como nos desafios anteriores).

### Importante: Proteção de branch

Antes de executar, configure a proteção da branch `main` como mostramos na live de quarta. Isso garante a qualidade do ciclo de revisão e evita merges diretos na `main` sem validações. Veja a demonstração aqui: [AO VIVO - Descomplicando Github Actions - Resolvendo Desafio](https://www.youtube.com/watch?v=VihvfGx58IY).

## Variável obrigatória

- Crie uma variável de repositório chamada `IMAGE_NAME` (em: Settings > Secrets and variables > Actions > Variables) com o nome da aplicação a ser usada no nome da imagem (ex.: `desafio3-linuxtips-gha`).
- Essa variável é obrigatória e será utilizada para compor o nome final da imagem no GHCR.

## Actions obrigatórias

Você deve utilizar exatamente estas actions nas versões a seguir:

- `docker/login-action@v3`
- `docker/build-push-action@v6`
- `aquasecurity/trivy-action@0.28.0`
- `docker/build-push-action@v6`

### Política de lint (Hadolint)

Para nós, os checks do Hadolint devem focar nos itens que mais impactam segurança e reprodutibilidade. Portanto, este pipeline deve falhar apenas quando forem detectadas as regras abaixo:

- DL3006: uso consistente e fixação do gerenciador de pacotes/base da imagem (garante builds reprodutíveis);
- DL3008: instalação de pacotes sem pin de versões (evita deriva de dependências e janelas de vulnerabilidade).

Por política da empresa e conformidade de supply chain, consideramos DL3006 e DL3008 bloqueadores.

## Regras de publicação da imagem

- **Registry**: `ghcr.io`
- **Tag obrigatória**: o SHA do commit (`${{ github.sha }}`)
- **Nome completo (exemplo)**: `ghcr.io/<owner>/<IMAGE_NAME>:<sha>`
- O nome completo deve ser convertido para minúsculas para evitar erros no push.

## Critérios de aceite

- [ ] Workflow Nível 3 dispara somente após PR mergeado na `main`.
- [ ] `Dockerfile` analisado com **Hadolint**; gerar artefato `lint-report.txt`.
- [ ] O workflow falha se Hadolint encontrar **DL3006** ou **DL3008**
- [ ] Build local com **Trivy**.
- [ ] Relatório `trivy-report.txt` gerado e publicado como artefato.
- [ ] Workflow falha se o scan encontrar vulnerabilidades CRITICAL.
- [ ] Smoke test da imagem executa `node --version` e falha se não houver saída.
- [ ] Push realizado no GHCR apenas se todas as verificações passarem.
- [ ] Uso das actions nas versões exigidas.

Boa sorte e nos vemos no sábado, dia 20/09, para resolver esse desafio na nossa live das 13h

#VAI
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:18-alpine AS base

WORKDIR /app

RUN apk add --no-cache gcompat=1.1.0-r4

COPY package*.json ./

RUN npm ci --omit=dev

COPY . .

ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

CMD ["node", "server.js"]


2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ <h3 id="testsCount">0</h3>
</section>

<section class="challenges-section">
<h2><i class="fas fa-trophy"></i> Desafios 01 e 02</h2>
<h2><i class="fas fa-trophy"></i> Desafios 01 a 03</h2>
<div class="challenges-grid" id="challengesGrid">
<!-- Desafios serão carregados dinamicamente -->
</div>
Expand Down
11 changes: 9 additions & 2 deletions public/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ const challenges = [
description: 'Implemente testes com Jest e atinja cobertura mínima de 80% no pipeline. Gere e faça upload do certificado do nível 2.',
badge: 'testes-automatizados',
reward: 'Badge: Desafio 02 Concluído'
},
{
id: 'containers-seguros',
title: 'Desafio 03 - Containers e Segurança',
description: 'Construa a imagem Docker, escaneie com Trivy e publique no ECR apenas se não houver vulnerabilidades CRÍTICAS. Gere e faça upload do certificado do nível 3.',
badge: 'containers-seguros',
reward: 'Badge: Desafio 03 Concluído'
}
];

Expand Down Expand Up @@ -229,7 +236,7 @@ function updateBadgesDisplay() {
// Atualizar seção do certificado
function updateCertificateSection() {
const certificateSection = document.getElementById('certificateSection');
const hasAnyCertificate = appState.badges.includes('first-steps') || appState.badges.includes('testes-automatizados');
const hasAnyCertificate = appState.badges.includes('first-steps') || appState.badges.includes('testes-automatizados') || appState.badges.includes('containers-seguros');

if (certificateSection) {
certificateSection.style.display = hasAnyCertificate ? 'block' : 'none';
Expand Down Expand Up @@ -553,7 +560,7 @@ Badges disponíveis: first-steps, testes-automatizados
window.onBadgeClick = function(badgeId) {
const username = document.getElementById('certificateUsername').value.trim();
if (!username) return;
const level = badgeId === 'testes-automatizados' ? 2 : 1;
const level = badgeId === 'containers-seguros' ? 3 : (badgeId === 'testes-automatizados' ? 2 : 1);
selectedCertificateLevel = level;
generateCertificate();
}
Expand Down
Loading