Skip to main content

Verificação de vulnerabilidades na AWS com a integração do Snyk

Escrito por
AWS vulnerability scanning

10 de fevereiro de 2021

0 minutos de leitura

Se você usa o conjunto de ferramentas da AWS relacionadas ao Kubernetes, vai gostar de saber que pode usar o Snyk para fazer verificações diretamente nos seus fluxos de trabalho, com integrações ao Amazon Elastic Container Registry ( ECR ) e ao Amazon Elastic Kubernetes Service ( EKS ). Veja como começar!

Neste post, vou usar um dos aplicativos de teste do Snyk para criar e implantar uma aplicação — você encontra o código aqui. Também será necessário instalar algumas dependências:

Vamos começar

Agora que está tudo pronto, vamos começar!

Primeiro, vamos configurar o ECR para que o Snyk possa acessar nossos repositórios. Para isso, precisamos atribuir uma política e uma função no AWS Identity and Access Management, concedendo ao backend do Snyk as permissões necessárias para, por exemplo, listar imagens e extraí-las dos nossos repositórios do ECR.

As etapas para configurar a integração estão descritas na documentação do Snyk, mas podemos encontrar todas as configurações necessárias diretamente na interface do Snyk. Acesse Settings/Integrations/ECR/Edit Settings. Primeiro, vamos configurar uma política usando o JSON exibido na interface:

{
"Version": "2012-10-17",
"Statement": [
 {
  "Sid": "SnykAllowPull",
  "Effect": "Allow",
  "Action": [
   "ecr:GetLifecyclePolicyPreview",
   "ecr:GetDownloadUrlForLayer",
   "ecr:BatchGetImage",
   "ecr:DescribeImages",
   "ecr:GetAuthorizationToken",
   "ecr:DescribeRepositories",
   "ecr:ListTagsForResource",
   "ecr:ListImages",
   "ecr:BatchCheckLayerAvailability",
   "ecr:GetRepositoryPolicy",
   "ecr:GetLifecyclePolicy"
  ],
  "Resource": "*"
 }
]
}

- oriIsso concederá ao Snyk as permissões definidas na seção Action do JSON para todos os repositórios e imagens da nossa conta do ECR. Esse é o conjunto mínimo de permissões necessário para listar repositórios e imagens e extrair imagens dos repositórios. Siga as instruções para adicionar esse JSON à configuração da política do IAM na AWS.

Em seguida, precisamos adicionar uma função e atribuir a política a ela, conforme descrito nas instruções:

Instruções para criar uma função do AWS IAM e implementar uma política, incluindo selecionar EC2, a política de acesso somente leitura ao ECR e dar a ela o nome SnykServiceRole

Também precisamos definir o escopo da função, especificando os IDs das organizações Snyk que poderão usá-la. Para isso, edite as relações de confiança da função recém-criada usando o JSON fornecido na documentação. A string sts:ExternalId será o ID da sua organização Snyk. Esse trecho de JSON permite que a função seja assumida pelo principal da AWS se o ExternalId informado corresponder ao ID da nossa organização Snyk.

{
"Version": "2012-10-17",
"Statement": [
 {
  "Effect": "Allow",
  "Principal": {
   "AWS": "arn:aws:iam::198361731867:user/ecr-integration-user"
  },
  "Action": "sts:AssumeRole",
  "Condition": {
   "StringEquals": {
    "sts:ExternalId": "11111111-1111-1111-1111-111111111111"
   }
  }
 }
]
}

Conforme descrito nas instruções, se você precisar adicionar várias organizações Snyk a essa política, inclua-as em uma matriz JSON entre colchetes:

"sts:ExternalId": [
"11111111-1111-1111-1111-111111111111",
"22222222-2222-2222-2222-222222222222",
]

Você encontra o ID da sua organização Snyk acessando Settings > General.

Página de configurações mostrando a aba Geral com os campos de nome da organização, chave de API e ID da organização.

A última etapa é configurar a interface do Snyk para usar essa função e se conectar ao ECR. Volte para Settings/Integrations/ECR/Edit Settings e informe a região em que o ECR está configurado e a função que criamos anteriormente:

Formulário de credenciais da conta para conectar o Snyk a uma conta do Amazon ECR, com campos para região da AWS, ARN da função e salvar alterações

Agora, a integração com o ECR deve estar totalmente configurada. Vamos avançar e testar.

Primeiro, vamos usar a AWS CLI para criar um repositório no ECR:

% aws ecr create-repository --repository-name goof
{
    "repository": {
        "repositoryArn": "arn:aws:ecr:us-west-2:478468688580:repository/goof",
        "registryId": "478468688580",
        "repositoryName": "goof",
        "repositoryUri": "478468688580.dkr.ecr.us-west-2.amazonaws.com/goof",
        "createdAt": "2021-02-03T13:40:59+00:00",
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": false
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}

Agora que temos um repositório no ECR, vamos configurar o Docker para enviar imagens a ele:

% aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 478468688580.dkr.ecr.us-west-2.amazonaws.com

Esse comando exibirá sua senha de login e a enviará para a CLI do Docker, que vai autenticar você no registro do AWS ECR. Confira se a região está definida corretamente para aquela em que o ECR está habilitado.

Agora, vamos conferir nossa aplicação de teste. É uma aplicação simples em Node.js, com várias vulnerabilidades e configurações para criá-la no Docker e implantá-la no Kubernetes.

% git clone https://github.com/mattj-io/goof.git goof
% cd goof

Primeiro, vamos criar a aplicação usando o Docker.

% docker build -t goof .

Depois que a imagem for criada, ela deverá aparecer no repositório local do Docker:

% docker images
REPOSITORY                                TAG      IMAGE ID       CREATED      SIZE
goof                                      latest    7934ddc2fec9   2 days ago   1.04GB

Antes de enviá-la para nosso repositório ECR, precisamos adicionar uma tag, substituindo o nome do repositório pelo que você configurou anteriormente no ECR:

% docker tag goof:latest 478468688580.dkr.ecr.us-west-2.amazonaws.com/goof:latest
% docker images
REPOSITORY                                          TAG       IMAGE ID       CREATED      SIZE
478468688580.dkr.ecr.us-west-2.amazonaws.com/goof   latest    7934ddc2fec9   2 days ago   1.04GB
goof                                      latest    7934ddc2fec9   2 days ago   1.04GB   

Agora podemos enviá-la ao ECR:

% docker push 478468688580.dkr.ecr.us-west-2.amazonaws.com/goof:latest 

Quando o envio terminar, podemos conferir no repositório do ECR a imagem que acabamos de enviar:

% aws ecr list-images --repository-name goof
{
    "imageIds": [
        {
            "imageDigest": "sha256:ca6c19e25b4d7917769ee535f0b073e04e8ddc32ead83493c03abc65e82e5e6c",
            "imageTag": "latest"
        },
        {
            "imageDigest": "sha256:cd100d7c505ced1f5c4f4eb6fbd7ac83a623f9bb483db1e828fb4cd3b3c01bd9"
        }
    ]
}

Quando a imagem estiver no ECR, podemos configurar o Snyk para verificá-la. Na interface do Snyk, selecione Add Project e acesse o ícone do ECR:

Menu de configuração de projeto do Snyk com opções de integração para GitHub, Docker Hub, ECR, Kubernetes, CLI e outras

Se a configuração do ECR estiver correta, você verá uma tela com todos os repositórios e as imagens disponíveis neles:

Tela de seleção de repositório do Snyk com um campo de nome da imagem e caixas de seleção para os repositórios aws-ecr, goof e latest

Vamos encontrar o repositório goof que criamos antes, com uma única tag de imagem. Selecione a imagem e clique no botão Add selected repositories. O Snyk importará o repositório do ECR e começará a verificá-lo e monitorá-lo.

Quando a importação terminar, ela deverá aparecer na página Projects do Snyk:

Painel do repositório mostrando o projeto goof e o arquivo package.json mais recente, com selos de status e horários dos testes recentes

O Snyk identificou a própria imagem e o arquivo package.json usado pela aplicação Node, implantado na imagem, e criou projetos para ambos.

A imagem usa uma imagem-base antiga e vulnerável. O Snyk detectará várias vulnerabilidades e recomendará imagens-base que podem reduzir a quantidade total de vulnerabilidades.

Resultados da análise de vulnerabilidades da AWS, com recomendações para atualizar a imagem base mais recente do Docker do Goof, além da quantidade de vulnerabilidades e dos níveis de gravidade.

O arquivo package.json define quais pacotes Node foram incluídos na própria aplicação, tanto diretamente, pelos pacotes especificados no arquivo package.json, quanto indiretamente, como dependências. O Snyk criará uma árvore de dependências com todos esses pacotes e consultará o banco de dados de vulnerabilidades para encontrar problemas nas versões específicas usadas. Você verá que há muitas vulnerabilidades, pois esta aplicação foi projetada para usar versões vulneráveis.

Relatório de vulnerabilidade do pacote Goofys que mostra um problema de alta gravidade de gravação arbitrária de arquivos durante a extração de arquivos compactados e detalhes sobre como corrigir o problema

Você também encontrará informações sobre cada vulnerabilidade e recomendações de correção indicando quais pacotes atualizar. Explore as informações disponíveis na interface do Snyk.

Como configurar a integração do Snyk com o EKS

Agora, vamos configurar a integração do Snyk com o Elastic Kubernetes Service. Para isso, você precisará iniciar uma avaliação gratuita de um dos planos padrão do Snyk, pois a integração com o Kubernetes faz parte da nossa oferta paga.

Para isso, vamos criar um cluster do Kubernetes usando o EKS. Há várias maneiras de fazer isso, mas uma das mais simples é usar a ferramenta eksctl.

Primeiro, uso a AWS CLI para criar um par de chaves que posso usar para acessar os nós do cluster por SSH, se necessário:

% aws ec2 create-key-pair --key-name demo --query "KeyMaterial" --output text > demo.pem

Em seguida, vou usar eksctl para criar um cluster chamado mattjarvis-sko na região us-west-2, com um grupo de nós gerenciados do Linux e adicionando meu par de chaves.

% eksctl create cluster --name mattjarvis-sko --region us-west-2 --with-oidc --ssh-access --ssh-public-key SKO_demo --managed

A execução desse comando levará algum tempo, mas, quando terminar, você terá um cluster EKS totalmente funcional e o kubectl estará configurado para se comunicar com ele.

% kubectl get nodes
NAME                                           STATUS   ROLES    AGE    VERSION
ip-192-168-24-115.us-west-2.compute.internal   Ready    <none>   2d5h   v1.18.9-eks-d1db3c
ip-192-168-84-146.us-west-2.compute.internal   Ready    <none>   2d5h   v1.18.9-eks-d1db3c

Agora que nosso cluster EKS está funcionando, vamos conferir a configuração do Kubernetes para implantar a aplicação. No checkout do repositório Git da aplicação goof, você encontrará um diretório chamado manifests, que contém dois arquivos YAML do Kubernetes.

% ls manifests
goof-deployment.yaml goof-service.yaml

O arquivo goof-deployment.yaml define a aplicação goof e um pod do mongodb, necessário como dependência. Você precisará alterar a seção da imagem para usar o repositório ECR configurado anteriormente:

% cat manifests/goof-deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: goof
spec:
  replicas: 1
  selector:
    matchLabels:
      app: goof
      tier: frontend
  template:
    metadata:
      labels:
        app: goof
        tier: frontend
    spec:
      containers:
        - name: goof
          image: 478468688580.dkr.ecr.us-west-2.amazonaws.com/goof
          resources:
            requests:
              cpu: 100m
              memory: 100Mi
          ports:
            - containerPort: 3001
            - containerPort: 9229
          env:
            - name: DOCKER
              value: "1"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: goof-mongo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: goof
      tier: backend
  template:
    metadata:
      labels:
        app: goof
        tier: backend
    spec:
      containers:
        - name: goof-mongo
          image: mongo
          ports:
            - containerPort: 27017

O arquivo goof-service.yaml define o serviço para mongodb, fornecendo conectividade ao pod mongodb na porta 27017. É assim que nossa aplicação goof se conecta ao banco de dados. Ele também define um serviço LoadBalancer externo, que será implantado para disponibilizar externamente a aplicação em execução. A implementação do LoadBalancer depende da plataforma em que o cluster está implantado. Na AWS, será provisionado um Elastic Load Balancer, com a porta 80 exposta externamente e direcionando o tráfego para a porta 3001 do nosso pod goof, onde está a aplicação Node.js.

% cat manifests/goof-service.yaml   
apiVersion: v1
kind: Service
metadata:
  name: goof
spec:
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3001
    name: "http"
  - protocol: TCP
    port: 9229
    targetPort: 9229
    name: "debug"
  selector:
    app: goof
    tier: frontend
---
apiVersion: v1
kind: Service
metadata:
  name: goof-mongo
spec:
  ports:
  - protocol: TCP
    port: 27017
    targetPort: 27017
    name: "mongo"
  selector:
    app: goof
    tier: backend

Vamos implantar a aplicação no cluster:

FIXME TO HERE

% kubectl create -f manifests/goof-deployment.yaml
% kubectl create -f manifests/goof-service.yaml

% kubectl get pods
NAME                         READY   STATUS    RESTARTS   AGE
goof-5589f855f8-hzvjw        1/1     Running   0          2d13h
goof-mongo-775d5b7d8-w4658   1/1     Running   0          2d13h

% kubectl get services
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP                                                               PORT(S)                       AGE
goof         LoadBalancer   10.100.94.189   adae6cf9abdb84b63919cc27c2ecafcb-1847540121.us-west-2.elb.amazonaws.com   80:32621/TCP,9229:31301/TCP   2d13h
goof-mongo   ClusterIP      10.100.97.98    <none>                                                                    27017/TCP                     2d13h
kubernetes   ClusterIP      10.100.0.1      <none>          

A URL no campo EXTERNAL-IP é fornecida pelo ELB provisionado como parte do serviço LoadBalancer. Podemos usá-la para acessar a aplicação em execução. Se acessarmos essa URL pelo navegador, veremos a aplicação goof funcionando:

Interface minimalista de um app de lista de tarefas, com o título “Goof TODO” e um campo de texto vazio.

Nossa aplicação já está implantada e funcionando no cluster EKS. A última etapa é configurar a integração entre o Snyk e nosso cluster EKS, para que possamos verificar as cargas de trabalho em execução na produção. A integração do Snyk com o Kubernetes consiste em um único operador Kubernetes, executado em um pod, que consulta a API do Kubernetes, verifica imagens de contêineres dentro do cluster e se comunica com o backend do Snyk.

Se estivéssemos usando o CloudFormation para implantar o cluster EKS, poderíamos usar os Quick Starts da AWS que criamos para implantar a integração do Snyk. Como estamos usando o eksctl nesta demonstração, vamos fazer a instalação manualmente.

Primeiro, vamos adicionar o repositório Helm que contém os charts para implantar o monitor do Snyk para Kubernetes:

helm repo add snyk-charts https://snyk.github.io/kubernetes-monitor/

Agora, vamos criar um namespace separado para executar o monitor do Snyk. Em geral, é uma boa prática criar namespaces separados para suas aplicações no Kubernetes, pois isso permite controles de permissões mais granulares.

kubectl create namespace snyk-monitor

A próxima etapa é criar um secret no Kubernetes contendo nosso integrationID do Snyk. O processo do monitor do Snyk usará esse valor para se comunicar com a API do Snyk e fornecer informações sobre os pods em execução no cluster. Se estivéssemos usando registros privados que exigem login para baixar imagens, também precisaríamos incluir as informações de autenticação na seção dockercfg.json. Saiba mais na documentação do monitor do Snyk.

kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=dockercfg.json={} --from-literal=integrationId=11111111-1111-1111-1111-111111111111

Depois de criar o secret, podemos usar o Helm para instalar o Snyk Monitor no namespace snyk-monitor.

helm upgrade --install snyk-monitor snyk-charts/snyk-monitor --namespace snyk-monitor --set clusterName="Production"

Quando o chart do Helm terminar de ser executado, veremos o pod snyk-monitor em execução no namespace snyk-monitor do cluster:

% kubectl get pods -n snyk-monitor
NAME                            READY   STATUS    RESTARTS   AGE
snyk-monitor-589cff67c7-kcj8j   1/1     Running   0          2d22h

Também podemos consultar os logs e confirmar que o snyk-monitor está enviando dados ao Snyk:

% kubectl logs snyk-monitor-589cff67c7-kcj8j
----snipped for brevity-----
{"name":"kubernetes-monitor","hostname":"snyk-monitor-589cff67c7-kcj8j","pid":6,"level":30,"workloadLocator":{"userLocator":"eb30b15b-a0a5-47a4-ab75-7e50b853d6a9","cluster":"Production","namespace":"default","type":"Deployment","name":"goof"},"attempt":1,"msg":"workload metadata sent upstream successfully","time":"2021-02-04T10:43:42.789Z","v":0}
{"name":"kubernetes-monitor","hostname":"snyk-monitor-589cff67c7-kcj8j","pid":6,"level":30,"workloadLocator":{"userLocator":"eb30b15b-a0a5-47a4-ab75-7e50b853d6a9","cluster":"Production","namespace":"default","type":"Deployment","name":"goof-mongo"},"attempt":1,"msg":"workload metadata sent upstream successfully","time":"2021-02-04T10:43:42.821Z","v":0}

Pode levar um tempo até que as cargas de trabalho apareçam na interface do Snyk, pois o monitor precisa enviar os dados ao Snyk. Mas, em cerca de um minuto, você poderá ver as cargas em execução no cluster acessando Add Project/Kubernetes:

Menu de criação de projetos com opções para GitHub, Docker Hub, ECR, Kubernetes, CLI, repositórios públicos do GitHub e Outros

Agora, as cargas de trabalho em execução deverão aparecer. Para adicioná-las, basta selecionar a caixa de seleção para que o Snyk as importe e teste.

Tela de seleção de workloads do Kubernetes mostrando o ambiente Production, os namespaces default e synke-monitor e opções de implantação.

Depois de importar o projeto, podemos acessar a página Projects e abrir o projeto importado. Primeiro, observe que o Snyk detectou a configuração em execução da carga de trabalho e verificou se havia problemas de segurança. Aqui, vemos que ela não passou em vários testes de segurança, incluindo a execução como root e sem limites de CPU definidos:

Relatório de segurança da implantação do Kubernetes para default/deployment.apps/goof, mostrando vulnerabilidades, dependências, metadados e verificações de configuração segura.

Ao analisar a própria carga de trabalho, vemos que o Snyk detectou a imagem-base, todas as vulnerabilidades nela presentes e recomendou imagens-base alternativas com menor exposição a vulnerabilidades.

Página de detalhes de uma imagem no AWS ECR mostrando os resultados da análise de vulnerabilidades e recomendações de atualização para uma imagem de contêiner

Para concluir

Esta demonstração mostrou como podemos começar a integrar testes de segurança em todo o ciclo de vida do desenvolvimento de software, uma estratégia essencial para manter a segurança das nossas cargas de trabalho e da infraestrutura em ambientes nativos da nuvem.

O Snyk também se integra a diversas ferramentas de ambiente de desenvolvimento integrado (IDE), gerenciamento de código-fonte e CI/CD, para que você possa garantir a cobertura em todo o pipeline de implantação.

Se você usa os serviços de contêineres e Kubernetes da Amazon, é muito fácil integrar o Snyk ao ECR e ao EKS. Também há Quick Starts da AWS para implantar tudo isso usando modelos do CloudFormation. Crie uma conta gratuita do Snyk e experimente!

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.