• Nenhum resultado encontrado

Execução segura com contentores

N/A
N/A
Protected

Academic year: 2021

Share "Execução segura com contentores"

Copied!
87
0
0

Texto

(1)

Universidade de Aveiro 2020

JOÃO MARQUES

(2)

Universidade de Aveiro 2020

JOÃO MARQUES

CORREIA

EXECUÇÃO SEGURA COM CONTENTORES

Dissertação apresentada à Universidade de Aveiro para cumprimento dos requisi-tos necessários à obtenção do grau de Mestre em Engenharia de Computadores e Telemática, realizada sob a orientação científica do Doutor André Ventura da Cruz Marnoto Zúquete, Professor auxiliar do Departamento de Eletrónica, Telecomuni-cações e Informática da Universidade de Aveiro.

(3)

o júri

presidente Professor Doutor Joaquim João Estrela Ribeiro Silvestre Madeira Professor Auxiliar da Universidade de Aveiro

vogais Professor Doutor Nuno Miguel Carvalho dos Santos Professor Auxiliar do Instituto Superior Técnico da Universidade de Lisboa

Professor Doutor André Ventura da Cruz Marnoto Zúquete Professor Auxiliar da Universidade de Aveiro

(4)

Palavras Chave Segurança, Contentores, Vulnerabilidades, Segurança em Contentores, Linux, Tec-nologias de Contentorização, Virtualização.

Resumo A disponibilização de software com recurso a contentores está a tornar-se no método de eleição por parte das grandes empresas de tecnologia, por possibilitar às equipas empacotar, testar e disponibilizar as suas aplicações sem os problemas e frustrações que surgem da transição entre ambientes.

No entanto, levantam-se algumas preocupações relativamente à segurança e isolamento providenciado pelos contentores. Contrariamente ao que se pensa, os contentores são extremamente vulneráveis por omissão, não só devido aos seus privilégios, mas também por dependerem da segurança de vários componentes, como o núcleo Linux, espaços de nomes e as imagens utilizadas para lançar o contentor.

Para responder a estas preocupações e encontrar uma solução definitiva para estes problemas, estudou-se nesta dissertação a vanguarda das tecnologias de contentorização, assim como as principais preocupações de segurança com contentores. O objetivo último foi encontrar a arquitetura de contentorização mais segura, tal como quais as configurações a efetuar para lançar o contentor da forma mais segura possível.

(5)

Keywords Security, Containers, Vulnerabilities, Container Security, Linux, Container Techno-logies, Virtualization.

Abstract Containerized software is becoming the preferred method of distribution by the big technology companies, granting dev teams the ability to assemble, test and deploy their applications without the problems and drawbacks that come with shifting development environments.

However, some questions arise regarding container security and isolation. Unlike popular belief, containers are exceptionally vulnerable by default, not only because of their privileges, but also because of their dependency on the Linux Kernel, namespaces and the required container images.

To appease the concerns regarding container security, and to find a defini-tive solution to these problems, a study was performed on the leading edge virtualization technologies, in addition to the most important container security subjects. The final objective was to find the safest container architecture configuration, as well as the required configurations to be performed in order to create the safest container possible.

(6)

Conteúdo

Conteúdo i

Lista de Figuras iii

Glossário vi 1 Introdução 1 1.1 Problema . . . 2 1.2 Objetivo . . . 2 1.3 Estrutura . . . 2 1.4 Trabalhos Relacionados . . . 3 2 Fundamentos de Segurança 5 2.1 Segurança . . . 5 2.1.1 Confidencialidade . . . 6 2.1.2 Integridade . . . 6 2.1.3 Disponibilidade . . . 7 2.1.4 Não-repúdio . . . 7 2.2 Segurança em Software . . . 7

2.2.1 Segurança por Omissão . . . 7

2.2.2 Privilégio Mínimo . . . 8

2.2.3 Defesa em Profundidade . . . 8

2.3 Vulnerabilidades . . . 8

2.3.1 OWASP Top 10 . . . 9

2.3.2 Os Sete Reinos Promíscuos da Segurança em Software . . . 9

2.4 Exploração de Vulnerabilidades . . . 10

2.4.1 Violação de Dados . . . 11

2.4.2 Distúrbio de Serviços . . . 12

(7)

3 Conceitos de Virtualização 13 3.1 Virtualizadores . . . 13 3.1.1 Hipervisores . . . 14 3.1.2 Contentores . . . 15 3.2 Tecnologias de Contentorização . . . 16 3.2.1 LXD . . . 17 3.2.2 Docker . . . 18 3.2.3 Podman . . . 20

3.2.4 Docker in Docker (dind) . . . 21

3.2.5 Tecnologia mais Segura . . . 21

3.3 Orquestradores . . . 22

3.3.1 Docker Swarm . . . 23

3.3.2 Kubernetes . . . 23

3.4 Primitivas de Segurança . . . 24

3.4.1 Namespaces . . . 25

3.4.2 Control Groups (cgroups) . . . 25

3.4.3 Secure computation mode (seccomp) . . . 26

3.4.4 Redução de Capacidades . . . 27

3.4.5 Restrições de Dispositivos e Ficheiros . . . 27

3.4.6 Controlo de Acessos . . . 28 4 Ameaças em Contentores 29 4.1 Vulnerabilidades do Linux . . . 29 4.1.1 User namespaces . . . 30 4.1.2 Container Runtime . . . 31 4.2 Recursos Comprometidos . . . 31 4.2.1 Aplicações Vulneráveis . . . 32 4.2.2 Imagens envenenadas . . . 32 4.3 Problemas de Configuração . . . 33 4.3.1 Isolamento Impróprio . . . 33 4.3.2 Capacidades em Excesso . . . 33

4.3.3 Configurações de Rede Fracas . . . 34

4.4 Demonstração de Ataques . . . 34

4.4.1 Distúrbio de Serviços . . . 35

4.4.2 ARP Spoofing . . . 38

4.4.3 Reverse Shell . . . 40

(8)

5 Segurança em Contentores 45

5.1 Configurações Seguras . . . 45

5.1.1 User Namespaces . . . 45

5.1.2 Remoção de Capacidades e Funcionalidades . . . 47

5.1.3 Limitação de Recursos . . . 48 5.1.4 Encobrimento da Rede . . . 48 5.1.5 Distribuição de Atualizações . . . 49 5.2 Ferramentas de Análise . . . 49 5.2.1 CSVS . . . 50 5.2.2 Docker Bench . . . 50 5.2.3 Clair . . . 52 5.2.4 Falco . . . 52 5.3 Verificações de Segurança . . . 53 5.3.1 Distúrbio de Serviços . . . 54 5.3.2 ARP Spoofing . . . 55 5.3.3 Reverse Shell . . . 56 5.3.4 Aumento de Privilégios . . . 57 5.4 Contentores Virtualizados . . . 58 5.4.1 Microkernels . . . 59 5.4.2 Lightvisors . . . 59 5.4.3 Docker Kata-Fc . . . 60

5.4.4 Linux Containers on Windows (LCoW) . . . 60

6 Cenário Real 62 6.1 Arquitetura da Aplicação . . . 63

6.1.1 Âmbito e Ambiente de Testes . . . 63

6.2 Testes de Segurança . . . 64 6.2.1 Específicos a Contentores . . . 65 6.2.2 Exemplos Genéricos . . . 67 6.3 Observações Finais . . . 70 7 Resultados 71 8 Conclusão 73 Referências 74

(9)

Lista de Figuras

2.1 Segurança como resultado da interseção da tríade CIA. . . 6

2.2 Número de fugas de informação nos Estados Unidos, em milhões [20]. . . 11

2.3 Custo médio por fuga de informação, em milhões de dólares [20]. . . 11

3.1 Virtualização de servidores. . . 14

3.2 Hipervisores (tipo 1 e 2) e máquinas virtuais. . . 15

3.3 Motor de contentorização e contentor. . . 16

3.4 Motor de contentorização LXC e runC. . . . 17

3.5 Relação entre LXC e LXD. . . 18

3.6 Arquitetura do Docker [26]. . . 18

3.7 Ilustração do funcionamento do Docker. . . 19

3.8 Ilustração do funcionamento do Podman. . . 20

3.9 Estudo comparativo entre tecnologias de contentorização. . . 21

3.10 Arquitetura alto nível do Swarm [39]. . . 23

3.11 Arquitetura alto nível do Kubernetes [32]. . . 24

3.12 Distribuição de recursos utilizando cgroups [9]. . . 25

3.13 Análise do desempenho do seccomp, num processador Intel i7 [45]. . . . 26

3.14 Limitação das capacidades do Linux [13]. . . 27

3.15 Ilustração da pilha do sistema de ficheiros de um contentor Docker. . . 28

4.1 Excerto de um email do criador do Linux, Linus Torvalds. . . 30

4.2 Excerto das imagens mais descarregadas. . . 32

4.3 Construção e lançamento da Fork Bomb. . . . 37

4.4 Exaustão de recursos e falha completa do sistema. . . 37

4.5 Lançamento e endereços dos contentores legítimos. . . 39

4.6 Ping e tabela de ARP dos contentores na máquina 2. . . . 39

4.7 Ataque (na máquina 3, em cima) e tabela ARP envenenada (na máquina 2, em baixo). . 39

4.8 Diagrama da rede no ambiente de teste. . . 41

4.9 Reverse shell com sucesso para um contentor. . . . 42

4.10 Fuga de um contentor seguido de reverse shell. . . . 44

4.11 Reverse shell para um contentor depois da fuga. . . . 44

5.1 Utilizador e grupos criados com user namespaces. . . . 46

5.2 Alteração no mapeamento de utilizadores nos diretórios dos contentores. . . 46

(10)

5.4 Exemplo de verificações presentes no CSVS. . . 50

5.5 Lançamento do Docker Bench. . . 51

5.6 Arquitetura do Clair [24]. . . 52

5.7 Monitorização de um contentor com recurso ao Falco. . . 53

5.8 Demonstração de um ataque de exaustão de recursos. . . 55

5.9 Demonstração de uma restrição de memória. . . 55

5.10 Endereços IP de dois contentores. . . 55

5.11 Ping entre dois contentores. . . . 56

5.12 Entradas na firewall to hospedeiro. . . . 56

5.13 Lançamento de uma reverse shell. . . . 56

5.14 Deteção de uma reverse shell. . . . 57

5.15 Tentativa de elevação de privilégios. . . 57

5.16 Falha na elevação de privilégios. . . 58

5.17 Comparação entre um núcleo monolítico (esquerda) e micro núcleo (direita). . . 59

5.18 Funcionamento do Linux Containers on Windows. . . 61

6.1 Arquitetura simplificada do Dataplaxe. . . 63

6.2 Ambiente de testes do Dataplaxe. . . 63

6.3 Reverse shell para o contentor na máquina virtual. . . . 65

6.4 Variáveis ambientais presentes no contentor. . . 66

6.5 Versão do núcleo do Linux, visto de dentro do contentor. . . 67

6.6 Serviços em execução dentro do contentor. . . 68

6.7 Executáveis com o bit SUID ativo. . . . 68

6.8 Excerto do ficheiro /etc/group. . . 69

(11)

Glossário

PaaS Platform as a Service

API Application Programming Interface

REST Representational State Transfer

TCP Transmission Control Protocol

PID Process Identification Number

UTS UNIX Time Sharing

IPC Inter-Process Communication

CoW Copy-on-Write

CPU Central Processing Unit

OWASP Open Web Application Security Project

CSVS Container Security Verification Standard

CIA Confidentiality, Integrity and Availability

DoS Denial-of-Service

DDoS Distributed Denial-of-Service

NIST National Institute of Standards and Technology

NSA National Security Agency

CVSS Common Vulnerability Scoring System

SQL Structured Query Language

OS Operating System

LDAP Lightweight Directory Access Protocol

XML Extensible Markup Language

XSS Cross-Site Scripting

I/O Input/Output

ISO International Organization for Standardization

IEC International Electronic Commission

LXC Linux Containers

OCI Open Container Initiative

AUFS Advanced Multi-layered Unification File System

CI Continuous Integration

AWS Amazon Web Services GCP Google Cloud Platform

CNCF Cloud Native Computing Foundation

MAC Mandatory Access Control

ARP Address Resolution Protocol

CNCF Cloud Native Computing Foundation

RWE Read-Write-Execute

CVE Common Vulnerabilities and Exposures

IP Internet Protocol

MAC Media Access Control

ICMP Internet Control Message Protocol

CIS Center for Internet Security

NVD National Vulnerability Database

vTPM Virtual Trusted Platform Module

SUID Set User ID

KDC Key Distribution Center

(12)

CAPÍTULO

1

Introdução

Os monitores de máquinas virtuais são atualmente a espinha dorsal da indústria tecnológica baseada em virtualização, mostrando-se resilientes e seguros [4]. No entanto, os contentores são mais fáceis e simples de gerir, pelo que se estão a tornar no método de distribuição de aplicações preferido por parte das grandes empresas de tecnologia, em deterioramento das infraestruturas baseadas em hipervisores.

Os contentores possibilitam às equipas de desenvolvimento empacotar, testar e disponibili-zar as suas aplicações sem os problemas e frustrações que surgem da transição entre ambientes virtualizados. De acordo com um relatório da Flexera, a utilização de contentores aumentou 14% em 2020, sendo agora utilizado em 65% das organizações [15]. As empresas encontraram nos contentores uma forma de aumentar a sua produtividade, conseguindo assim desenvolver e distribuir software com uma maior rapidez e comodidade.

Devido ao aumento acentuado de adoção e utilização por parte da indústria, as tecnologias de contentorização têm-se tornando um dos tópicos de maior discussão. Muitas destas giram à volta das preocupações com a sua segurança. Enquanto que os contentores oferecem bastantes vantagens a nível de gestão e administração, também é necessário ter noção de que a camada de isolamento que oferecem é relativamente mais fina que a dos hipervisores. Os contentores são compostos por vários componentes que têm de ser efetivamente protegidos. Quando mal configurados, os níveis de segurança que estes oferecem por omissão não são suficientes para prevenir a maior parte dos ataques, sendo extremamente vulneráveis a ataques de elevação de privilégios e execução remota de código.

Para além disso, os contentores são baseados no núcleo do sistema operativo do hospedeiro, o que os deixa à mercê das vulnerabilidades do mesmo. Em 2019 foram encontradas quase 180 vulnerabilidades no núcleo Linux que fragilizaram a segurança oferecida por vários contentores baseados neste sistema operativo [31]. Com o aumento constante da utilização e disseminação de contentores, torna-se imperativo estudar e entender os problemas de segurança que advêm do núcleo Linux, das tecnologias de contentorização, das imagens dos sistemas operativos e das suas configurações.

(13)

1.1 Problema

Apesar de se estarem a tornar no método padrão de desenvolvimento e distribuição de software, existe ainda alguma resistência à adoção de contentores por parte de algumas empresas. A maior barreira à disseminação desta tecnologia, e a principal preocupação, são as questões relacionadas com o isolamento dos contentores e com a eficácia das configurações padrão.

Para conseguir executar programas com segurança fazendo uso de contentores, é necessário estudar estes problemas e encontrar soluções. É necessário analisar e determinar as tecnologias de contentorização mais seguras e estudar os limites de segurança permitidos pelas primitivas dos sistemas operativos. Com o aumento da utilização de contentores, surge a necessidade de uma resposta definitiva quanto ao nível de isolamento dos mesmos, assim como informação sobre os mecanismos de segurança disponíveis e boas práticas de configuração.

Este projeto foi realizado em contexto empresarial na empresa Altice Labs. A necessidade desta dissertação surge precisamente do desenvolvimento de uma aplicação que faz uso extensivo de tecnologias de contentorização. As dificuldades em garantir a execução segura de

software nos contentores desta aplicação validam o problema lançado.

1.2 Objetivo

Esta dissertação tem como objetivo, numa primeira fase, fazer um estudo aprofundado das principais tecnologias de contentorização, por forma a conseguir dar uma resposta definitiva relativamente a qual tecnologia oferece o melhor nível de segurança e isolamento. Seguidamente, estudar quais as configurações seguras a efetuar aquando do lançamento de um contentor, com recurso a todas as ferramentas ao dispor, como primitivas do núcleo Linux e mecanismos de controlo de acessos, assim como analisar frameworks, padrões e ferramentas automáticas de análise com a finalidade de evitar as principais vulnerabilidades que podem ser introduzidas em imagens, quer involuntariamente, quer maliciosamente.

O verdadeiro problema não são as vulnerabilidades e problemas conhecidos, para os quais temos soluções. O perigo está nas novas vulnerabilidades, ainda desconhecidas, e que podem colocar sistemas em risco. Com isto em mente, pretende-se analisar as tecnologias mais recentes no mundo da virtualização, para encontrar uma solução que garante a segurança futura de aplicações e sistemas. O objetivo final é elaborar uma solução que aglutine definitivamente todas as tecnologias e configurações a executar para lançar o contentor mais seguro possível, respondendo assim às preocupações e problemas da atualidade relativamente às tecnologias de contentorização.

1.3 Estrutura

A dissertação está dividida em oito capítulos. Em cada um, os conteúdos são apresentados em ordem progressiva de complexidade, começando por uma introdução aos temas abordados.

O primeiro capítulo apresenta ao leitor o problema atual da segurança em contentores, assim como os objetivos e estrutura da dissertação.

(14)

No Capítulo 2 abordam-se os principais fundamentos de segurança, de uma forma simples e direta. Aqui, expõem-se as noções necessárias à compreensão dos temas abordados nos capítulos que se seguem.

O Capítulo 3 estuda de perto as tecnologias de contentorização e as primitivas do núcleo Linux, fundamentais para segurança. É feita também uma breve introdução a hipervisores, contentores e orquestradores, no sentido de facilitar a leitura das matérias seguintes.

O Capítulo 4 expõe os principais problemas de segurança em contentores, assim como as suas causas, com o objetivo de determinar vetores e superfícies de ataque. Como exercício, são mostrados resultados de lançamentos de contentores com imagens e aplicações vulneráveis, onde são executadas tentativas de exploração da rede, elevação de privilégios, distúrbio de serviços e execução remota.

O Capítulo 5 é o foco principal desta dissertação, onde se procede à configuração de um contentor com o maior nível de segurança possível. Seguidamente, usam-se algumas ferramentas de padronização e análise estática para verificar a segurança e qualidade das soluções escolhidas. À semelhança do capítulo anterior, realizam-se testes de penetração para se comprovar a eficácia das implementações. Finalmente, aborda-se a vanguarda das tecnologias de virtualização ao analisar as soluções mais recentes e inovadoras, nomeadamente utilização de contentores com recurso a hipervisores e microkernels, onde se procura uma solução que garanta a segurança de um contentor versus ataques do dia zero.

O Capítulo 6 valida o trabalho realizado através da análise de uma aplicação da Altice Labs. A aplicação analisada faz uso de contentores para executar código não seguro, pelo que é fundamental assegurar a robustez da sua implementação. Para isso, são aplicados os conhecimentos adquiridos e os testes apresentados nos Capítulos anteriores. São também realizados alguns exemplos mais genéricos de verificações.

No Capítulo 7 são apresentados aquilo que se consideram os resultados da dissertação. São um conjunto de boas práticas, sugestões, configurações e aplicações open-source necessárias à execução segura com contentores.

O Capítulo 8 conclui ao refletir sobre o trabalho realizado, dificuldades encontradas e futuros desenvolvimentos.

1.4 Trabalhos Relacionados

Embora o tema dos contentores e a subsequente segurança seja um tópico de elevada discus-são, o número de trabalhados diretamente relacionados é reduzido. Os poucos artigos que apresentam soluções para configurações seguras em contentores assumem sempre cenários bastante otimistas. No caso desta dissertação, todas as configurações foram sempre feitas tendo em conta o pior caso possível: que tanto as imagens do contentor como as aplicações nele contidas foram comprometidas.

Existem, no entando, algumas exceções, como o documento elaborado pelo NCC Group com o título de “Understanding and Hardening Linux Containers” [16]. Neste documento, são analisadas diversas tecnologias de contentorização, as vulnerabilidades que contêm e soluções para estes problemas. Alguma informação foi retirada deste documento, notoriamente

(15)

algumas tabelas relativas à comparação entre diversas tecnologias de contentorização. No entanto, pouco – ou quase nada – da restante dissertação foi baseada neste documento devido, precisamente, ao contexto em que são analisadas as vulnerabilidades. Mais uma vez, e contrariamente ao que é analisado no documento, esta dissertação assume o pior cenário possível de segurança, que envolve cenários de confiança zero [23].

Uma segunda exceção, apenas encontrada no final desta dissertação, é o artigo redigido por Sari Sultan, Imtiaz Ahmad e Tassos Dimitriou, com o título “Container Security: Issues,

Challenges, and the Road Ahead”, que analisa vários problemas de segurança em contentores,

mas de uma forma bastante teórica e sem demonstrações de ataques [44]. Apesar disso, são apresentadas no artigo várias soluções baseadas em hardware, como vTPM, que não foram consideradas nesta trabalho.

A última exceção também foi encontrada no final da dissertação, pelo que não teve impacto no seu desenvolvimento. Mesmo assim, não deixa de ser um documento interessante no sentido que comprova o interesse na área da segurança em contentores. O documento [36] em questão, desenvolvido pela OWASP e publicado pouco antes da conclusão desta dissertação em formato de rascunho e na sua maioria incompleto, apresenta uma abordagem ao modelo de ameaças em contentores, assim como uma lista das dez boas práticas mais importantes a ter aquando da configuração de contentores. O modelo de ameaças apresentado, embora à primeira vista parecido, tem por base pontos de vista distintos. Ao contrário do modelo criado nesta dissertação, o elaborado no documento OWASP vê as ameaças do ponto de vista do hospedeiro e mistura tipos de ataque (denial of service) com ameaças específicas a contentores (poisoned images). Enquanto que algumas sugestões apresentadas na lista de boas práticas são similares às que estão especificadas nos resultados desta dissertação, outras são completamente diferentes. Algumas sugestões vão muito além dos contentores, ao ponto de sugerir que não se misturem aplicações front-end e back-end no mesmo contentor. Não deixa de ser interessante que dois trabalhos iniciados praticamente ao mesmo tempo, sem conhecimento um do outro, tenham caminhado sensivelmente no mesmo sentido.

(16)

CAPÍTULO

2

Fundamentos de Segurança

Neste capítulo abordam-se os principais fundamentos de segurança necessários à compreensão das matérias seguintes. Pretende-se, através deste capítulo, sensibilizar o leitor para o conceito de “segurança”. Estes conceitos podem ser vistos como os alicerces ou a base de todos os conhecimentos de segurança, sendo transversais a todo este espaço. São fundamentais para entender e entrar no “mindset” de segurança.

É importante frisar que a informação aqui exposta não passa de uma gota num oceano que é esta disciplina. Tópicos como segurança em redes, desenvolvimento de software seguro, entre outros, estarão ausentes neste capítulo. Não por serem menos interessantes ou relevantes (pelo contrário), mas sim por fugirem do contexto e objetivo desta dissertação.

2.1 Segurança

A palavra segurança tem significados diferentes em contextos diferentes. Por norma, quando se fala em segurança, está na maioria das vezes a referir-se a segurança da informação. Pode definir-se segurança da informação como a proteção de sistemas ou informação de utilização e acesso indevido assim como divulgação ou alteração sem autorização, no sentido de garantir a confidencialidade, integridade e disponibilidade dos recursos [33]. A segurança da informação não é um fim, mas sim um meio para alcançar um fim. A incapacidade de garantir a segurança da informação por uma empresa ou instituição pode ter impactos desastrosos a nível legal e de reputação e, por isso, é importante que se adotem e implementem as regras e protocolos necessários para garantir a sua proteção [33].

No centro da segurança da informação está a trindade da confidencialidade, integridade e disponibilidade, também conhecida como CIA (do inglês CIA – Confidenciality, Integrity

and Availability). Estes três atributos são considerados os objetivos principais de qualquer

sistema seguro e são transversais a todas as disciplinas de segurança. Enquanto bastante conhecida e respeitada como um modelo de políticas de segurança, o trio CIA força uma vista limitada sobre segurança que tende a ignorar alguns aspetos importantes e, por isso,

(17)

mantém-se um debate sobre a sua eficiência em abordar certos aspetos como a interseção entre disponibilidade e confidencialidade, e as diferenças entre segurança e privacidade.

Figura 2.1: Segurança como resultado da interseção da tríade CIA.

A CIA não é um padrão rígido, mas sim um ponto de partida para a implementação de boas políticas de segurança por isso, e embora não presente no trio CIA, o não-repúdio será também apresentado como um requisito fundamental da segurança da informação.

2.1.1 Confidencialidade

Em segurança da informação, confidencialidade implica que a informação não esteja dispo-nível, nem seja divulgada a indivíduos, entidades ou processos não autorizados. Enquanto normalmente confundida com privacidade, as duas palavras não têm o mesmo significado. Privacidade é a habilidade e/ou direito de proteger a nossa informação pessoal [2]. De certo modo, a confidencialidade é uma componente da privacidade.

Em termos práticos, a confidencialidade implica que a informação que está guardada numa base de dados, e que é trocada entre um servidor e cliente, não possa ser revelada a terceiros. Para garantir a confidencialidade no acesso e troca de informação, é necessário implementar mecanismos bem definidos de autenticação e autorização.

2.1.2 Integridade

Em segurança da informação, integridade significa assegurar e manter a precisão e completude de dados armazenados ou trocados. Ou seja, proteger e impedir que os dados sejam modificados ou apagados por entidades não autorizadas e, caso sejam feitas alterações, estas possam ser revertidas. O conceito de integridade pode ser estendido a sistemas onde o objetivo é garantir que este realize as suas funções de forma desobstruída, livre de manipulação não autorizada acidental, ou intencional. Um utilizador deve conseguir confiar nos dados e na informação que recebe, que estes são legítimos, e que não foram adulterados de forma alguma.

(18)

2.1.3 Disponibilidade

Em segurança da informação, disponibilidade implica garantir que a informação está sempre pronta a ser acedida atempadamente [33]. Para um sistema de informação fazer sentido, a informação tem de estar disponível quando é precisa e, por isso, este é um dos requisitos mais importantes em segurança. Em muitos sistemas, é necessário garantir disponibilidade, por exemplo, de classe 5 (cinco noves – 99.999% do tempo). Na prática, garantir a disponibilidade de um sistema passa por prevenir, por exemplo, ataques de Denial-of-Service (DoS), falhas de energia ou avarias no hardware.

2.1.4 Não-repúdio

O não-repúdio é definido como a intenção de um indivíduo cumprir com as obrigações impostas por um contrato [3]. Em segurança da informação, implica que um sujeito ou coletivo não possa negar a posse ou conhecimento de um documento ou qualquer outra peça de informação que tenha sido assinada por estes. Esta assinatura é feita por meios criptográficos com recurso, por exemplo, a assinaturas digitais. O não-repúdio não é considerado na tríade CIA original porque tem como pré-requisitos a autenticidade e integridade.

2.2 Segurança em Software

Segurança em software é parte integrante da segurança da informação e, por isso, aplicam-se os mesmos princípios de confidencialidade, integridade e disponibilidade. Gary McGraw define segurança em Software Security: Building Security in como “a ideia de desenvolver software que continua a funcionar corretamente mesmo sob um ataque malicioso” [27]. Ou seja, a capacidade de um programa resistir, tolerar e recuperar de acontecimentos que intencionalmente põem em causa a sua fiabilidade.

Para conseguir que um programa funcione corretamente, mesmo quando está sob um ataque malicioso, é necessário assegurar a sua robustez, através da implementação de me-didas e técnicas que reduzem a probabilidade de erros, falhas e vulnerabilidades. De entre várias, destacam-se a segurança por omissão, o princípio do privilégio mínimo e a defesa em profundidade.

2.2.1 Segurança por Omissão

Segurança por omissão é, provavelmente, a abordagem mais utilizada para garantir a segu-rança em software. Um programa ou aplicação considera-se seguro por omissão quando foi desenvolvido desde o início com segurança em mente [42]. Segurança por omissão implica que a segurança seja um alicerce do programa a ser desenvolvido, ou seja, considerada uma das suas principais funcionalidades. Nenhum software no mundo real consegue atingir a segurança perfeita e, por isso, é necessário assumir que, eventualmente, será alvo de vulnerabilidades e ataques. O software deve ser desenvolvido com definições seguras por omissão e projetado para que, em caso de falha, falhe controladamente e de forma a não provocar risco. Na prática, passa por implementar uma arquitetura segura e robusta, fazendo uso de técnicas e padrões seguros reutilizáveis.

(19)

2.2.2 Privilégio Mínimo

O princípio do privilégio mínimo implica atribuir a um utilizador ou sistema somente os privilégios e as capacidades que este precisa para desempenhar as tarefas que lhe competem [40]. Assim, mesmo que um sistema seja comprometido e um atacante consiga ganhar acesso, o impacto que uma falha ou vulnerabilidade tem sobre o sistema é reduzido, visto que apenas possuirá um acesso limitado. Relativamente às restantes técnicas, é a forma mais fácil de conferir segurança, robustez e estabilidade ao sistema.

2.2.3 Defesa em Profundidade

A defesa em profundidade é uma técnica desenvolvida pela National Security Agency (NSA), e passa por implementar várias camadas de segurança, na forma de protocolos independentes, com o objetivo de atribuir redundância ao sistema [17]. Ao acoplar várias tecnologias e protocolos diferentes, eliminamos pontos únicos de falha do sistema e vetores de ataque simples. Assim, caso seja encontrada uma vulnerabilidade ou falha num sistema, um atacante tem de contornar todos os mecanismos e protocolos implementados antes de conseguir causar danos.

A implementação de múltiplos protocolos e mecanismos de segurança aumenta a com-plexidade do sistema e, por sua vez, a probabilidade da introdução de riscos. Isto vai de encontro ao princípio da simplicidade normalmente praticado em segurança. É necessário, portanto, encontrar um compromisso entre a complexidade e riscos do sistema, e a quantidade de mecanismos e protocolos a serem implementados. A defesa em profundidade, embora um conceito mais trabalhoso e complexo que o princípio do privilégio mínimo, quando usados em conjunto, formam uma barreira de segurança difícil de transpor.

2.3 Vulnerabilidades

O National Institute of Standards and Technology (NIST) define uma vulnerabilidade no contexto das tecnologias da informação como “uma falha ou fraqueza nos procedimentos, desenho, implementação ou controlos de segurança de um sistema que, quando utilizada (acidentalmente acionada ou intencionalmente explorada), pode resultar numa quebra de segurança ou na violação das políticas de segurança do sistema” [43]. O principal objetivo de segurança é precisamente combater e evitar estas vulnerabilidades.

Uma vulnerabilidade pode ter origem no software, como um bug ou uma falha de con-figuração, ou pode ser causada por um problema externo ao software, como uma falha no

hardware ou acesso indevido a equipamentos. Estes elos fracos são aquilo que permitem a um

atacante causar danos num sistema. Uma vulnerabilidade que não permita causar danos não deixa de ser uma vulnerabilidade, apenas deixa de ter um risco associado.

A Mitre Corporation disponibiliza uma lista de todas (ou maioria) das vulnerabilidades que foram divulgadas em aberto, classificadas de acordo com o Common Vulnerability Scoring

System (CVSS) [30]. A melhor forma de ajudar os programadores a entender os tipos mais

(20)

ou taxonomia de vulnerabilidades. O objetivo é que os programadores consigam detetar mais facilmente erros e falhas de segurança e que, eventualmente, aprendam a não repetir os mesmos erros. De entre as diversas criadas ao longos dos anos, destacam-se duas, com um elevado grau de adoção: o OWASP Top 10 e a taxonomia dos Sete Reinos Promíscuos.

2.3.1 OWASP Top 10

O OWASP Top 10 foi criado pela comunidade Open Web Application Security Project (OWASP) com o propósito de educar programadores para o perigo da introdução involuntária de vulne-rabilidades [37]. Todas as falhas presentes na lista podem ser derivadas de más configurações, erros no código ou defeitos na arquitetura do sistema e, por isso, são suscetíveis de serem evitadas através de boas práticas de codificação e desenvolvimento de software, como segurança por omissão, em profundidade, privilégio mínimo e codificação segura.

O OWASP Top 10 é uma ferramenta poderosa para a criação de aplicações Web e representa o consenso entre um número elevado de profissionais de todo o mundo sobre quais as vulnerabilidades mais críticas. Os 10 elementos da lista disponibilizada em 2017 são, por ordem de importância decrescente:

1. Injeção de código (SQL, NoSQL, OS e LDAP). 2. Quebra de autenticação.

3. Exposição de dados sensíveis. 4. Entidades externas de XML. 5. Quebra no controlo de acesso.

6. Configurações de segurança incorretas. 7. Cross-Site Scripting (XSS).

8. Desserialização insegura.

9. Utilização de componentes com vulnerabilidades conhecidas. 10. Monotorização e log ineficiente.

A lista do OWASP Top 10 descreve um conjunto de vulnerabilidades concretas normalmente consideradas úteis para o lado mais ofensivo da segurança, como a realização de testes de penetração ou verificações de segurança.

2.3.2 Os Sete Reinos Promíscuos da Segurança em Software

Do outro lado do espectro, temos a taxonomia dos Sete Reinos promíscuos [46]. Em vez de um conjunto de vulnerabilidades, apresenta uma taxonomia de erros de codificação e, por isso, tem um cariz mais passivo. Por norma, esta última é mais útil para o lado defensivo da segurança. Não obstantes, são ambas abordagens válidas e necessárias.

Publicada pela primeira vez no artigo “Seven Pernicious Kingdoms: A Taxonomy of

Software Security Errors” por Katrina Tsipenyuk, Brian Chess e Gary McGraw, esta taxonomia

apresenta-se como uma alternativa ao OWASP Top 10 (entre outras). Um argumento apresentado pelos autores da taxonomia para a sua superioridade em relação ao OWASP Top 10 é o facto deste último apresentar no mesmo nível de abstração tanto vulnerabilidades como erros de codificação (configurações de segurança incorretas são um tipo de erro, enquanto que

(21)

de abstração mais elevado que o OWASP Top 10, tanto é que todas as categorias do OWASP Top 10 podem ser mapeadas nas categorias desta taxonomia.

A taxonomia dos Sete Reinos Promíscuos está dividida em dois níveis hierárquicos: reinos e divisões (no artigo chamados de phyla). Foram escolhidos 7(+1) reinos porque os seres humanos, em geral, são bons a memorizar 7 coisas (mais ou menos duas). Cada reino inclui uma quantidade variável de divisões. Os reinos representam as classes de erros, enquanto que as divisões de cada reino representam os erros em específico [46]. Os reinos são os seguintes, por ordem decrescente de importância para a segurança em software:

1. Validação e representação dos dados introduzidos. 2. Abusos de API. 3. Funcionalidades de segurança. 4. Estado e tempo. 5. Erros. 6. Qualidade do código. 7. Encapsulamento. 8. Ambiente

Por funcionarem em níveis de abstração diferentes, e por terem como alvo espectros de segurança diferentes, não considero correto falar-se da superioridade de uma em relação a outra. Trata-se sim, na minha opinião, de adotar a taxonomia ou categorização mais apropriada para o objetivo em questão. Para educar programadores e arquitetos de sistemas, sem dúvida que a taxonomia dos Sete Reinos Promíscuos terá uma eficácia maior, não só por ser de mais fácil memorização, mas também pelo nível de abstração superior. Quando se põem em causa a realização de testes de penetração ou verificações de segurança, considero mais apropriado a utilização de uma lista de vulnerabilidades concreta, como o OWASP Top 10. Mais uma vez, ambas válidas e necessárias.

2.4 Exploração de Vulnerabilidades

A não consideração dos princípios fundamentais de segurança pode levar ao aparecimento de erros no software. Estes erros, se facultarem um meio de ataque, podem ser considerados como vulnerabilidades. O objetivo de segurança é precisamente combater o aparecimento ou a exploração destas vulnerabilidades. Contudo, isso nem sempre é possível. Nenhum programa ou equipamento é completamente seguro e impenetrável – a segurança perfeita é uma miragem. Os atacantes exploram as vulnerabilidades existentes com o propósito de cumprir com os seus objetivos, que podem ir desde obtenção de informação até ao simples vandalismo.

A forma como estes ataques são realizados pode ser classificada de duas formas relati-vamente à sua proximidade e interação com o alvo: remoto e local. Um ataque remoto é realizado através da rede ou Internet e, por isso, não precisa de estar fisicamente próximo do sistema alvo. Por outro lado, os ataques locais implicam uma interação prévia com o sistema alvo e que o atacante tenha já algum tipo de privilégios a nível do utilizador. Para os ataques locais poderem causar danos, por norma, é necessário que o atacante consiga elevar os seus privilégios para além dos de um utilizador normal.

(22)

Figura 2.2: Número de fugas de informação nos Estados Unidos, em milhões [20].

Independentemente da vulnerabilidade, da forma que é explorada ou do nível de proximidade, os ataques têm sempre como objetivo a violação de dados, distúrbio de serviços ou execução de código.

2.4.1 Violação de Dados

A grande maioria dos ataques têm como motivo, de uma maneira ou outra, a violação de dados ou obtenção de informação. Estas fugas de informação, que podem ser ou não intencionais, implicam sempre a obtenção ou má utilização de informação privada ou privilegiada por parte dos atacantes, como informação bancária ou propriedade intelectual.

Figura 2.3: Custo médio por fuga de informação, em milhões de dólares [20].

A normal ISO/IEC 27040 define violação de dados como “uma falha de segurança ou vulnera-bilidade que leva à destruição, perda, alteração ou acesso indevido ou acidental de informação protegida e privada quer seja ela transmitida, armazenada ou processada” [21].

(23)

2.4.2 Distúrbio de Serviços

Por vezes, as vulnerabilidades não são exploradas para violar dados, nem sequer para obter recompensas monetárias. Estes ataques podem ter origem em vinganças, ativismo ou puro vandalismo. Num ataque de distúrbio de serviços (DoS), um atacante tenta tornar os recursos, servidores ou computadores ligados à Internet temporariamente ou indefinidamente indisponíveis para os seus utilizadores.

Estes ataques são tipicamente alcançados através do envio de mensagens malformadas ou sem sentido para a máquina alvo, numa tentativa de sobrecarregar os sistemas e impedindo as comunicações legítimas. Para dificultar a deteção, o distúrbio de serviços pode ser feito de forma distribuída (DDoS). Assim, o tráfego enviado é originário de múltiplas fontes, tornando impossível parar o ataque com o simples bloqueio de uma origem.

2.4.3 Execução de Código

Nem todas as vulnerabilidades permitem a um atacante atingir o seu objetivo de imediato, seja eles violação de dados ou distúrbio de serviços. Por vezes, os recursos a que o atacante quer ter acesso estão protegidos de aplicações ou utilizadores não autorizados. Nesses casos, os atacantes têm de recorrer à elevação de privilégios para ganhar o mesmo nível de acesso que outros utilizadores do sistema, por exemplo administrador. O resultado é uma aplicação com mais privilégios que aqueles a que tinha direito, o que permite a realização de operações indesejadas. O atacante pode então utilizar estes novos privilégios para roubar informação protegida, instalar vírus e malware, ou executar código arbitrariamente.

A execução de código arbitrária por si só apenas garante ao atacante os mesmos privilégios da aplicação que continha a vulnerabilidade, pelo que um ataque de execução arbitrária de código é, por norma, precedido por uma elevação de privilégios.

(24)

CAPÍTULO

3

Conceitos de Virtualização

Devido à sua facilidade e flexibilidade, os contentores estão a tornar-se no meio de eleição por parte das grandes empresas para empacotar e distribuir aplicações [4]. Os contentores combi-nam as capacidades de isolamento do núcleo do Linux com a flexibilidade das metodologias de implementações baseadas em imagens. Embora já implementados nos sistemas operativos Windows 10 e Windows Server 2016, a grande maioria das implementações de contentores acontecem em ambiente Linux [11]. Com isso em mente, esta dissertação irá focar-se apenas nas tecnologias e implementações de contentores em sistemas operativos Linux.

Para compreender melhor o funcionamento de um contentor, ajuda também ter um entendimento sobre tecnologias similares, como hipervisores e máquinas virtuais. Portanto, neste capítulo, faz-se um contraste entre estas duas tecnologias de virtualização expondo, também, as diferentes implementações de contentores em sistemas Linux, numa tentativa de encontrar a tecnologia que nos possibilita a maior capacidade de isolamento e segurança. Por último, e para entender como um contentor garante o seu isolamento, estudam-se as primitivas do núcleo Linux do ponto de vista de segurança, como namespaces e cgroups, assim como ferramentas para controlo de acessos.

3.1 Virtualizadores

A virtualização é o processo de criação de uma versão virtual de um elemento, seja ele hardware ou software. Esta versão virtual toma a forma de uma camada de abstração que, no caso do

hardware, permite a partilha dos elementos de hardware reais – processador, memória,

arma-zenamento, etc. – entre múltiplos sistemas virtuais [19]. Esta partilha de recursos possibilita que vários sistemas operativos executem simultaneamente no mesmo computador físico. Cada sistema virtual (ou máquina virtual) comporta-se como um computador independente, mesmo que esteja a correr apenas numa porção do hardware real, visto que não têm conhecimento da divisão efetuada abaixo da camada de abstração.

A virtualização proporciona a segmentação de um sistema – como um servidor – em partes mais pequenas, para que possa ser utilizado por um número elevado de utilizadores ou

(25)

aplicações com necessidades diferentes. Isto leva a um aumento da eficiência de um sistema físico e a um melhor aproveitamento dos recursos. Atualmente a virtualização é uma norma na arquitetura das empresas de tecnologia e é o motor que impulsiona o desenvolvimento da computação na nuvem. Esta tecnologia permite que os utilizadores tenham apenas de adquirir os recursos computacionais na nuvem de que necessitam, e escalar estes recursos à medida que as suas necessidades aumentam [19].

Figura 3.1: Virtualização de servidores.

De entre as várias abordagens e tipos de virtualização, destacam-se a virtualização total e a virtualização a nível do sistema operativo. A virtualização total faz uso de um software hipervisor que interage diretamente com a camada física do sistema, como processador e memória. Este hipervisor mantém todos os sistemas virtuais a executar em cima deste completamente independentes, permitindo que cada um execute a sua própria versão de sistema operativo. Por outro lado, a virtualização a nível do sistema operativo não faz uso de um hipervisor. Esta capacidade é inerente ao próprio sistema operativo do hospedeiro, que é responsável por garantir a independência e distribuição de recursos entre cada virtualização. A desvantagem da virtualização a nível do sistema operativo é que cada sistema virtual é obrigado a executar a mesma versão de sistema operativo que o hospedeiro. Em contrapartida, esta abordagem impõem uma sobrecarga inicial (overhead) menor relativamente à virtualização total porque as aplicações dentro do sistema virtualizado utilizam as mesmas chamadas que o sistema operativo hospedeiro e, por isso, não necessitam de nenhuma emulação ou virtualização intermediária.

3.1.1 Hipervisores

Um hipervisor é o software ou firmware (ou até mesmo hardware) que gere a virtualização dos recursos e que é responsável pela criação e gestão das máquinas virtuais. Os hipervisores podem ser implementados diretamente no sistema físico (tipo 1), ou sobre o sistema operativo do hospedeiro (tipo 2). O primeiro tipo de hipervisor oferece vantagens significativas a nível de desempenho e estabilidade. Ao interagir diretamente com o hardware do sistema físico, elimina a necessidade de um sistema operativo intermédio e, por isso, interage diretamente com os dispositivos para realizar operações de Input/Output (I/O) ou processamento.

(26)

Neste caso, a primeira coisa a ser instalada no sistema físico é o hipervisor. O lado negativo é a redução da compatibilidade com o hardware devido às limitações dos drivers incluídos no hipervisor.

Figura 3.2: Hipervisores (tipo 1 e 2) e máquinas virtuais.

O segundo tipo de hipervisor tem a vantagem de abstrair o hardware do sistema físico, o que leva a uma maior compatibilidade entre equipamentos. O hipervisor, que está entre o sistema operativo do hospedeiro e a máquina virtual, tem então a responsabilidade de coordenar o

hardware necessário. Efetivamente, comporta-se como uma aplicação normal.

Como as máquinas virtuais executam uma versão do sistema operativo diferente da do hospedeiro, é necessária uma camada para gerir este sistema operativo convidado, o hipervisor. As máquinas virtuais são essencialmente uma emulação de um computador real, empacotando todas as bibliotecas necessárias para executar aplicações, assim como a sua própria pilha de hardware virtualizado. Do lado de dentro, a máquina virtual comporta-se como um sistema isolado e como se os seus recursos fossem reais, mas do lado de fora sabemos que se trata de um sistema virtualizado.

3.1.2 Contentores

Tanto as máquinas virtuais como contentores partilham um objetivo comum: o isolamento de aplicações e das suas dependências. A diferença é como atingem esse objetivo. Enquanto que numa máquina virtual a virtualização é feita ao nível do hardware, num contentor esta abstração é feita a um nível superior (ao nível do sistema operativo) com recurso a um motor de contentorização, em vez de um hipervisor. No entanto, e para todos os efeitos, um contentor comporta-se da mesma forma que uma máquina virtual no sentido em que contém o seu próprio espaço de processamento, interfaces privadas, endereço IP e capacidade para montar sistemas de ficheiros. A grande diferença é que um contentor partilha o núcleo do sistema operativo com o hospedeiro, o que os torna extremamente leves e eficientes.

Por definição, um contentor é uma unidade de software que empacota todo o código, bibli-otecas e dependências de uma aplicação, para que ela possa ser executada rápida e eficazmente

(27)

em qualquer ambiente computacional. Os contentores, por norma, são desenhados para correr apenas uma aplicação por contentor e executam sempre da mesma forma, independentemente da estrutura subjacente, o que os torna extremamente portáteis. Efetivamente, um contentor não passa de um processo supervisionado que partilha o núcleo com o sistema operativo hospedeiro e que possui limitações e translações no acesso a recursos.

Figura 3.3: Motor de contentorização e contentor.

A camada que separa os contentores do hospedeiro é relativamente fina, tendo em conta que os contentores partilham o núcleo do sistema operativo com o hospedeiro. Adicionalmente, um contentor pode realizar chamadas ao sistema operativo hospedeiro, enquanto que uma máquina virtual tem o seu próprio sistema operativo. Quando a segurança é um requisito importante, as máquinas virtuais são possivelmente a escolha mais acertada, visto que estão isoladas pela virtualização do hardware.

3.2 Tecnologias de Contentorização

Os contentores são efetivamente uma virtualização a nível do sistema operativo. Da mesma forma que uma máquina virtual necessita de um hipervisor para a criar e gerir os seus recursos de hardware, também um contentor precisa de uma espécie de “hipervisor” para gerir os recursos do sistema operativo hospedeiro. Atualmente, existem essencialmente duas tecnologias disponíveis para esse efeito: o Linux Containers (LXC) e o runC. Estes motores de execução comunicam diretamente com o núcleo do sistema operativo, e têm a responsabilidade de criar o contentor ao nível mais baixo.

O LXC é um método de virtualização a nível do sistema operativo que permite correr múltiplos sistemas operativos Linux isolados (contentores) em cima de um sistema operativo Linux hospedeiro [22]. O LXC não fornece uma máquina virtual, visto que não efetua virtualização a nível do hardware, mas em vez disso disponibiliza um ambiente virtual com o seu próprio processador, memória, rede e I/O. Estes ambientes virtuais podem correr versões do sistema operativo diferentes da do hospedeiro. Os contentores criados desta forma oferecem o ambiente mais próximo possível de uma máquina virtual, mas sem a sobrecarga da simulação do hardware.

(28)

A alternativa é o motor de contentorização universal runC que lança e executa contentores de acordo com a norma Open Container Initiative (OCI) [14]. Não só é extremamente leve e portátil, como foi desenhado de raiz com segurança em mente. Em contraste com o LXC, o objetivo do runC não é criar um sistema operativo isolado, mas sim executar uma aplicação isoladamente. O núcleo de um contentor criado pelo runC é partilhado com o sistema operativo do hospedeiro e, por isso, o isolamento é conseguido através da utilização de objetos do modelo computacional do núcleo Linux, como namespaces e cgroups.

Figura 3.4: Motor de contentorização LXC e runC.

Tanto o runC como o LXC podem ser configurados só por si. No entanto, esta configuração é extremamente demorada e onerosa, para além de implicar controlar manualmente todo o tipo de configurações e a inclusão de bibliotecas necessárias. A solução passa por recorrer a tecnologias de contentorização de alto nível que abstraem estas configurações de mais baixo nível, fornecendo assim uma experiência de utilização mais agradável e um aumento na rapidez do lançamento de contentores.

3.2.1 LXD

O LXD é um gestor de contentores que, como é possível adivinhar pelo nome, corre por cima do LXC. De uma maneira simples, pode definir-se o LXD como uma extensão do LXC. Acima de tudo, oferece uma forma mais amigável para o utilizador gerir e criar contentores baseados em LXC, transmitindo uma experiência similar a máquinas virtuais. O LXD é um serviço (daemon) com uma API do tipo REST sobre sockets UNIX locais ou IP, o que permite que este seja acedido local ou remotamente [7].

As vantagens da utilização do LXD não se ficam só pela experiência do utilizador. Enquanto que é preciso um processo independente do LXC por cada contentor, utilizando o LXD é possível lançar um número elevado de contentores LXC com um único gestor. O LXD foi desenhado para ser a melhor escolha quando a necessidade é gerir ambientes virtuais de longa duração, ou quando é preciso virtualizar um sistema operativo integralmente. A desvantagem do LXD é que apenas suporta Linux como o sistema operativo do hospedeiro, para além da única distribuição documentada para utilização ser o Ubuntu [22].

(29)

Figura 3.5: Relação entre LXC e LXD.

3.2.2 Docker

O Docker é uma Platform as a Service (PaaS) que permite criar contentores com aplicações, e executá-las em qualquer sistema Linux1. Como os contentores são leves, um único servidor pode correr vários contentores em simultâneo. Um estudo realizado em 2018 [10] mostrou que o uso típico do Docker envolve correr oito contentores por hospedeiro, mas que um quarto das organizações analisadas corre pelo menos 18. Assim, como um contentor normal, e ao contrário de uma máquina virtual, um contentor Docker faz uso do núcleo do hospedeiro.

Figura 3.6: Arquitetura do Docker [26].

Inicialmente, o Docker fazia uso do LXC para criar os seus contentores ao nível mais baixo, mas recentemente alterou a sua arquitetura, sendo agora implementado por cima do containerd e runC. O containerd era uma camada que pertencia ao Docker e que era responsável por

1Um contentor Docker pode também ser executado em Windows, desde que sejam feitas as devidas

(30)

controlar e comandar o runC. Ao separar esta componente do código principal, o Docker expôs mais uma camada de abstração, mais simples e menos poderosa, e principalmente focada para sistemas embebidos.

Para funcionar, um contentor Docker faz uso de três componentes [12]: o Docker daemon, um tipo de aplicação cliente-servidor, um repositório (registry) e um cliente. O cliente fala com o Docker daemon através de uma API do tipo REST, sobre sockets UNIX ou IP. Estes

daemons podem funcionar tanto no mesmo sistema como em sistemas separados, sendo possível

ligar um cliente a um Docker daemon remotamente. O Docker daemon recebe instruções do cliente e é responsável por gerir os contentores e imagens associadas. É este que faz a restituição das imagens, armazenadas no repositório, e que inicia os contentores através do

containerd e consequentemente o runC. O cliente apenas pede ao daemon para fazer estas

operações em nome dele. De uma forma simples, o daemon é que faz todo o trabalho. O repositório armazena as imagens, podendo fazê-lo tanto num repositório privado como público. Por omissão, utiliza o Docker Hub, que é um repositório público de imagens. As imagens não são nada mais do que um exemplo (template) só de leitura, com instruções para criar um contentor Docker. Por norma, são baseadas em imagens pré-existentes, como por exemplo Ubuntu, com algum nível de customização adicional.

Figura 3.7: Ilustração do funcionamento do Docker.

Finalmente, os contentores são uma instância das imagens, que podem ser iniciados, parados, movidos ou apagados utilizando a interface de linha de comandos do cliente. Um contentor é definido pela imagem e pelas configurações fornecidas aquando da sua criação. Quando um contentor é removido, quaisquer alterações que foram feitas e não tenham sido guardadas em armazenamento persistente, desaparecem. Por omissão, os contentores são relativamente bem isolados, tanto de outros contentores como do hospedeiro, sendo possível controlar este nível de isolamento através do uso das funcionalidades do sistema Linux, como namespaces e

(31)

3.2.3 Podman

O Podman [26] é uma alternativa ao Docker. Diferem apenas num aspeto crucial: o Podman não faz uso de um daemon, interagindo diretamente com o repositório e realizando ele próprio as tarefas de criação e gestão dos contentores. Em vez de utilizar um daemon, comunica diretamente com o núcleo do Linux e com o motor de contentorização runC. Os processos dos contentores são assim subprocessos do Podman, num modelo tradicional fork/exec, em vez de cliente-servidor, como no Docker. Este primeiro modelo traz algumas vantagens e funcionalidades não disponíveis no segundo.

Um aspeto diferenciador, e talvez o mais importante, é o facto de o Podman não necessitar de ser executado em modo privilegiado, em contraste com o Docker daemon, que necessita destes privilégios para funcionar corretamente. Ao se atribuir privilégios elevados ao Docker

daemon, estamos a colocar-nos à mercê dos mecanismos de segurança implementados no

Docker daemon e a colocar um peso adicional na proteção do hospedeiro onde este está a ser executado.

Nas versões mais recentes do Docker, já está disponível uma versão “rootless” do daemon, mas com algumas limitações, como a incompatibilidade com o cgroups2 , que impossibilita a atribuição e restrição da utilização dos recursos físicos do hospedeiro (processador, memória) por parte do contentor. Atualmente, existe uma versão atualizada, cgroupsv2, que possibilita a limitação de recursos por parte de utilizadores não privilegiados, mas ainda não está implementada no Docker.

No entanto, o Docker também tem alguns aspetos a seu favor. O principal, e mais óbvio, é o facto de ser a tecnologia mais utilizada para criar e gerir contentores [10]. Ao estar tão disseminado, leva a que todo o seu código já tenha sido minuciosamente escrutinado na tentativa de encontrar falhas e vulnerabilidades. Quando encontradas, estas vulnerabilidades são, por norma, rapidamente corrigidas. O Podman, por oposição, é relativamente recente e pouco utilizado e por isso o grau de escrutínio e confiança é menor.

Figura 3.8: Ilustração do funcionamento do Podman.

Outra vantagem que o Docker tem sobre o Podman são as suas configurações por omissão. Mais uma vez bastante estudadas, as configurações por omissão do Docker fornecem já de raiz um nível de segurança e isolamento decente, reduzindo assim a probabilidade de se introduzirem vulnerabilidades por falhas ou ausência de configuração.

(32)

3.2.4 Docker in Docker (dind)

O dind é um caso particular da utilização do Docker, em que um contentor Docker inicia ele próprio uma instância de um contentor, executando um contentor Docker dentro de outro. O principal propósito do dind é facilitar o desenvolvimento do próprio Docker, para além de ser bastante utilizado em Continuous Integration (CI) juntamente com o Jenkins [38]. No entanto, existem alguns casos em que é empregue na tentativa de melhorar o nível de segurança e isolamento de um contentor Docker. Infelizmente, esta implementação não melhora o nível de segurança, muito pelo contrário, como iremos ver.

Para um contentor conseguir ele próprio criar um contentor, é necessário que este corra em modo privilegiado (flag “--privileged”), devido à necessidade do Docker daemon correr com privilégio root. Adicionalmente, impossibilita a utilização de mecanismos de segurança complementares como o SELinux e AppArmor devido a conflitos entre os contentores [38]. Isto leva a que a fuga de dentro de um contentor não só seja relativamente mais fácil, mas bastante mais perigosa.

O caso mais grave é na utilização do sistema de ficheiros. O contentor externo corre por cima de um sistema de ficheiros normal, mas o contentor interno executa por cima de um sistema de ficheiros Copy-on-Write (CoW), que é a solução implementada pelo Docker. Um sistema de ficheiros AUFS não pode correr em cima de outro sistema AUFS, o que força a utilização de atalhos e soluções alternativas com segurança reduzida [38].

Como tal, a utilização de um contentor Docker dentro de outro contentor tem o efeito completamente oposto ao desejado e deve ser evitado, se o propósito for o aumento do isolamento e nível de segurança.

3.2.5 Tecnologia mais Segura

O numero de soluções disponíveis tem vindo a aumentar, acompanhando o aumento acentuado na popularidade das tecnologias de contentorização. No entanto, todas diferem entre si quanto ao nível de seguraça que permitem atingir. Tecnologias diferentes disponibilizam ferramentas diferentes, assim como requisitos e configurações por omissão.

(33)

No documento “Understanding and Hardening Linux Containers”, redigido pelo NCC Group [16], é apresentado um estudo comparativo entre as diversas tecnologias de con-tentorização. Na tabela que resultou do estudo, representada na Figura 3.9, é possível observar a liderança do Docker em relação às restantes tecnologias no que diz respeito a funciona-lidades e configurações por omissão. Algumas destas funcionafunciona-lidades, como a capacidade de impedir a atribuição de novos privilégios, conferem ao Docker um nível de segurança superior. As configurações por omissão também são uma grande vantagem, ao retirar alguma da responsabilidade na configuração por parte de utilizadores menos experientes.

A solução do Podman encontra-se ausente neste estudo. No entanto, as capacidades e configurações por omissão que apresenta são, quase na sua maioria, iguais às do Docker. Como referido em tópicos anteriores, devido a ser tão recente, o Podman está repleto de bugs e vulnerabilidades que, mesmo apesar de não necessitar de privilégios root para ser utilizado, o coloca atrás de tecnologias como o Docker. Embora presente no estudo, o CoreOS Rkt não foi tido em conta nesta dissertação, não só devido às fracas configurações por omissão, mas também pela impossibilidade de adicionar outras, excluindo-o logo à partida.

Tendo por base este estudo, e as conclusões retiradas dele, é assumido nesta dissertação que a tecnologia que possibilita um maior nível de segurança, através de ferramentas e configurações, é o Docker. Como tal, as demonstrações e capítulos seguintes incidirão principalmente sobre esta tecnologia.

3.3 Orquestradores

Os contentores alteraram dramaticamente a forma como as organizações desenvolvem software, ao permitir facilmente empacotar e distribuir as suas aplicações. Em arquiteturas de micro-serviços, as aplicações são partidas e empacotadas em diferentes contentores, não só pela capacidade de escalarem mais facilmente, mas também devido à simplicidade em lançar e destruir contentores conforme as necessidades.

No entanto, isto levanta alguns desafios operacionais. Enquanto que gerir uma dezena de aplicações e contentores é relativamente fácil, se este número for ampliado para os milhares, torna-se um desafio esmagador. Para operar em grande escala é necessário recorrer a mecanis-mos de orquestração de contentores que automatizam todo o processo de lançamento, gestão, rede e escalabilidade de contentores. As tecnologias de orquestração permitem acima de tudo:

• Aprovisionar e lançar contentores.

• Escalar ou remover contentores para distribuir uniformemente a carga aplicacional. • Mover contentores entre hospedeiros caso haja falta de recursos.

• Alocar recursos entre contentores.

• Monitorizar a saúde de contentores e hospedeiros.

Atualmente, é possível utilizar estas ferramentas de orquestração, como o Docker Swarm e Kubernetes, em praticamente todos os ambientes que suportam o lançamento de contentores, quer sejam servidores, quer sejam tecnologias em nuvem como o Amazon Web Services (AWS), Google Cloud Platform (GCP) ou Microsoft Azure.

(34)

3.3.1 Docker Swarm

O Docker Swarm é uma plataforma open-source e é o orquestrador por omissão do Docker, desenvolvido por e para este. Qualquer software, serviço ou ferramenta que é utilizada em contentores Docker funciona igualmente bem no Swarm, para além do facto que ambos utilizam as mesmas linhas de comandos, e do Swarm vir incorporado por omissão no Docker. O objetivo do Swarm é transformar um conjunto de sistemas hospedeiros físicos num único hospedeiro virtual. Estes sistemas físicos podem ser locais ou estar hospedados na cloud.

Quando se usa o Swarm, por norma, trabalha-se a um nível de abstração mais elevado relativamente ao Docker, em que a unidade principal são os serviços e não os contentores em si. O Swarm realiza a gestão e orquestração destes serviços com recurso a unidades operacionais chamadas nodes.

Figura 3.10: Arquitetura alto nível do Swarm [39].

Um Swarm não é nada mais que a junção de um ou mais Docker Engine/Daemon, onde cada um pertence a somente um node do tipo worker ou manager. Um node do tipo manager, como o nome indica, tem a responsabilidade de gerir os workers e é a unidade responsável pelo processo de gestão, orquestração e monitorização de todos os contentores e recursos no Swarm [6]. Os serviços a serem executados são divididos em tarefas e atribuídos a contentores que correm dentro de um worker node.

3.3.2 Kubernetes

O Kubernetes é um orquestrador portátil, extensível e open-source. Inicialmente desenvolvido pela Google, com a ajuda de mais de uma década e meia de experiência em cargas distribuídas de trabalho em grande escala, foi mais tarde doado à Cloud Native Computing Foundation (CNCF) [32]. A unidade de mais alto nível no Kubernetes é um cluster, que é equivalente a um swarm no Docker Swarm. Um cluster é um conjunto de máquinas chamadas de nodes, que podem ser tanto máquinas virtuais, como servidores físicos. Cada node corre o seu próprio motor de contentorização, como o containerd.

(35)

Figura 3.11: Arquitetura alto nível do Kubernetes [32].

À semelhança do Swarm, um cluster tem de ter pelo menos um worker node e um manager

node. A diferença é que dentro de um node existem ainda pods, que contêm os componentes

das aplicações e serviços. Um pod é uma coleção de um ou mais contentores (geralmente apenas um), e é a principal unidade de gestão do Kubernetes. Os limites de recursos (CPU, memória, etc.) de um pod são definidos aquando do lançamento de um cluster.

Comparativamente ao Docker Swarm, o Kubernetes é um orquestrador bastante mais complexo e poderoso, e é a melhor escolha quando é necessária a capacidade de escalar e gerir automaticamente um elevado número de contentores. Adicionalmente, o Swarm apenas suporta a tecnologia de nuvem Microsoft Azure, enquanto que o Kubernetes suporta, para além desta, as soluções da Google e da Amazon.

3.4 Primitivas de Segurança

Os contentores adicionam aos sistemas uma camada extra de proteção ao isolarem as aplicações dos hospedeiros, e minimizando ao mesmo tempo a utilização de recursos. Ao utilizar as capacidades de isolamento inerentes ao núcleo do Linux, como namespaces e cgroups, é possível executar um contentor isolado numa única instância de um sistema Linux, sem a perda de desempenho de correr e manter uma máquina virtual. A maior parte das tecnologias de contentorização têm acesso às seguintes primitivas:

• Namespaces.

• Control groups (cgroups).

• Secure computing mode (seccomp). • Linux kernel capabilities.

• Device and file restriction.

• Mandatory Access Control (MAC).

As primitivas do núcleo do Linux são mais do que blocos de construção de contentores – são também elas que definem o nível de segurança e isolamento. Diferentes tecnologias de contentorização têm acesso a diferentes primitivas de segurança, assim como implementações e configurações distintas. Quanto mais funcionalidades estiverem implementadas, e quanto melhor configuradas, maior a segurança de um contentor.

(36)

3.4.1 Namespaces

Os namespaces do Linux são usados para isolar um sistema naquilo que definimos como um contentor. Quando um contentor é iniciado, é criado um conjunto de namespaces para esse contentor em específico, que o isola dos restantes e do hospedeiro. Os namespaces são a forma mais simples e eficaz de isolamento: um processo a correr dentro de um contentor não consegue ver, e muito menos interagir, com um processo dentro de outro contentor, ou mesmo do hospedeiro.

O user namespace, introduzido recentemente, introduz a capacidade de restringir e atribuir capacidades a cada namespace individualmente. Isto permite que utilizadores não privilegiados consigam ter privilégios elevados dentro de um espaço isolado, ao mapear o utilizador root dentro do contentor a um non-root fora do contentor. Um processo pode agora executar em modo privilegiado dentro de um contentor, embora fora deste continue com privilégios reduzidos. Os namespaces disponíveis são [13]:

PID Namespace: os PID associados aos programas executados no contentor pertencem a um namespace diferente do utilizado no hospedeiro.

MNT Namespace: cada contentor possui o seu namespace para diretórios montados.NET Namespace: cada contentor tem a sua vista própria da rede evitando acessos

privilegiados a sockets ou interfaces de outros contentores, ou do hospedeiro.

UTS Namespace: permite o isolamento entre dois elementos específicos do sistema relacionados com a syscall uname.

IPC Namespace: os contentores só podem ver e comunicar com processos no seu próprio Inter-Process Communication (IPC) namespace.

• User Namespace: isolam identificadores e atributos relacionados com segurança, em particular IDs de utilizador e grupo.

3.4.2 Control Groups (cgroups)

Os control groups são uma funcionalidade do núcleo do Linux que permite controlar os recursos a que cada contentor tem acesso. Os cgroups permitem que o motor de contentorização consiga gerir e dividir os recursos disponíveis a serem atribuídos a cada contentor e, se necessário, definir limites para cada contentor, como por exemplo o limite máximo de memória disponível ou utilização do processador [12].

Referências

Documentos relacionados

É considerado proponente o usuário Pessoa Física que inscreve o projeto no Sistema de Captação de Projetos Culturais Online.. É considerado representante legal a Pessoa

Este trabalho foi realizado com o objetivo de avaliar a quantidade de lodo de esgoto produzido pela ETE Belém, estação que produz lodo aeróbio e utiliza a caleação como método

Após 96 horas, houve um aumento no consumo, com o aumento de 100 para 160 ninfas, que não diferiu significativamente da densidade 220; com 280 ninfas disponíveis houve um

Para sua sorte, você está mergulhando nas páginas de um livro que visa, mais que apenas lhe ensinar sobre o trading esportivo, mostrar como pode ser lucrativo – não

“E deve-se, tal como nos outros casos, pondo adiante os φαινóµενα e tendo primeiro examinado suas dificuldades, demonstrar, assim, por um lado, sobretudo todos os

As terapias com os parâ- metros utilizados (teste) para o laser não promoveram diferença significativa na temperatura superficial local, no entanto, para o LED pode

Profilo in alluminio a U, 17x8x2000mm atEna1010-S Estribo para fixação Staffa di fissaggio atEna1010-t Tampa para fechamento Tappo di chiusura atEna1010-F Frente em

The aims of this study were to evaluate FGF-2 expression in different categories (primordial, primary, secondary and antral follicles) and follicular compartments