Conteúdo


Um guia do desenvolvedor orientado a comportamento para infraestrutura como código

Aplicando BDD à provisão de servidor usando Ansible

Comments

No início dos anos 2000, Kent Beck declarou "seguir estas duas regras simples pode nos levar a trabalhar muito mais perto do nosso potencial: gravar um teste automatizado de falha antes de gravar qualquer código e remover a duplicação". Mais tarde, Dan North expandiu essa orientação com o Desenvolvimento Orientado a Comportamento (BDD).

Dan tentou resolver o problema com o Desenvolvimento Orientado a Testes (TDD) no qual os desenvolvedores precisavam "saber por onde começar, o que testar e o que não testar, o quanto de cada vez, como denominar os testes e como entender por que um teste falha." Ele fez isso criando um modelo de cenário (ou um critério de aceitação) para analisar uma história do usuário e fornecer um método para executá-lo (JBehave). Isso transforma os requisitos (histórias do usuário) em documentos ativos direcionando o comportamento do aplicativo. Este artigo amplia essa questão para usar as mesmas técnicas para gerar Infraestrutura como código (IaC) a fim de provisionar um servidor para o aplicativo usando Ansible.

Histórias para cenários

Uma história do usuário é uma parte da funcionalidade (algumas pessoas usam a palavra recurso) que tem valor para o cliente.

Martin Fowler e Kent Beck, Planning Extreme Programming (Addison-Wesley Professional, 2000)

Além do critério INVEST (Independente, Negociável, Valioso, Estimável, Pequeno, Testável), uma história do usuário deve ser escrita de uma maneira fechada, ou seja, deve terminar "com a conquista de uma meta significativa" (Mike Cohn, "User Stories Applied: For Agile Software Development") para o usuário. Um formato típico a ser usado para capturar isso é o modelo de história do usuário Connextra:

Como {função}
eu desejo {objetivo/desejo} para que {benefício}

A partir dessas histórias, os cenários ou os critérios de aceitação podem ser gerados. Geralmente, as histórias do usuário são o "caminho mais tranquilo" no aplicativo. Agora, esse caminho e suas alternativas precisam ser explorados. Dan North usa o modelo given-when-then (dado-quando-então) para os cenários:

Dado algum contexto inicial (o fornecido),
Quando um evento ocorre,
Então alguns resultados são garantidos.

Uma linguagem popular baseada neste modelo é Gherkin. Depois de ser capturado, ele pode se tornar executável por meio de uma estrutura como Cucumber ou JBehave.

Cenários de TI

Qualquer infraestrutura técnica deve ser criada em conjunto com as histórias e deve ser desenvolvida para dar suporte àquilo que as histórias precisam.

Martin Fowler & Kent Beck, Planning Extreme Programming (Addison-Wesley Professional, 2000)

Geralmente, as tarefas básicas de infraestrutura relacionadas a um projeto são realizadas durante a primeira iteração, chamada Zero Feature Release (ZFR ou ziffer). Algumas vezes chamada de fina, ela é apenas a infraestrutura suficiente para dar suporte ao aplicativo e ao seu desenvolvimento. A infraestrutura normalmente é definida como restrições no sistema, como "o aplicativo deve usar o CouchDB como o banco de dados" ou "a equipe de desenvolvimento usará o Jenkins para a integração contínua". Embora nem todas as restrições precisem ser expressas com o modelo de história do usuário, mais tarde será interessante entender a origem e o motivo da restrição.

Histórias de segurança

Usuários finais de desktop, dispositivo móvel ou console de jogo podem não exigir especificamente que o aplicativo do servidor seja executado na versão mais recente do servidor da web Apache. No entanto, os comentários que resultam no critério de aceitação podem aparecer nas entrevistas e nos questionários dos usuários. A perda de dados do usuário pode ser extremamente prejudicial para o usuário. Quando a PlayStation Network da Sony foi hackeada, isso fez com que usuários e políticos questionassem sobre a ausência de correção e de firewall no servidor. Atentar para o impacto de uma violação de segurança, da perda de dados, do tempo de inatividade ou de outros eventos relacionados a TI gera cenários para a sua infraestrutura.

Lista 1. História do usuário inicial
Como usuário do seu serviço
Eu desejo que as informações do meu cartão de crédito sejam protegidas
Para que eu não precise cancelar e solicitar outro cartão.

Agora o problema com essa história é que ela não está fechada e é difícil de ser elaborada em cenários. Um método conhecido é elaborá-la com histórias de usuários mal intencionados. Essas são histórias elaboradas a partir do ponto de vista de um agente mal intencionado.

Lista 2. Caso de uso do hacker mal intencionado 1
Como hacker, eu
desejo fazer a varredura de porta do servidor para que eu possa
ver se há serviços menos seguros e vulneráveis em execução.
Lista 3. Caso de uso do hacker mal intencionado 2
Como hacker
Eu desejo aproveitar as vulnerabilidades em pacotes "desatualizados"
Para que eu possa obter acesso ao sistema

Essas histórias de usuário mal intencionado agora podem ser elaboradas em cenários que as impedem, como:

Lista 4. Caso de uso do hacker mal intencionado 1: cenário 1
Visto que o servidor tenha um firewall instalado
Quando uma varredura de porta for executada
Então somente a porta SSL 443 será aberta

Um recurso da linguagem Gherkin é o conceito de estrutura de tópicos do cenário, que aplica o cenário a um conjunto de dados.

Lista 5. Caso de uso do hacker mal intencionado 2: cenário 1
Estrutura de tópicos do cenário: esperar que as
atualizações de segurança sejam instaladas
   Dado que o servidor seja o Ubuntu 14.04
   E o pacote <package>
   Quando a versão for buscada
   Então ela deverá ser igual ou posterior à versão <version>
   Exemplos: pacotes nginx do Ubuntu 14.04 com atualizações de segurança
     | pacote     | versão          |
     | nginx        | 1.4.6-1ubuntu3.7 |
     | nginx-common | 1.4.6-1ubuntu3.7 |
     | nginx-core   | 1.4.6-1ubuntu3.7 |

Criando um projeto de provisão

Há muitas estruturas de IaC disponíveis, como Chef para SaltStack. Este artigo utiliza o Ansible baseado em Python. É possível instalá-lo executando pip install ansible na linha de comando.

Infelizmente, o Ansible sozinho não oferece nenhum teste de unidade nem recurso de linting. O comando ansible-galaxy init <playbook name> cria um projeto de função de estrutura básica, mas somente fornece um mecanismo para testes manuais. Ferramentas como a ServerSpec ajudam, mas não são suficientes. O Molecule oferece melhores práticas de desenvolvimento de software adicionais para o desenvolvimento da função Ansible.

O Molecule oferece o seguinte fluxo de trabalho básico:

  1. Verificar a sintaxe da função (ansible-lint).
  2. Criar as imagens virtuais usadas para testar por meio de um wrapper Vagrant. Como alternativa, ele também oferece suporte para o Docker e OpenStack.
  3. Convergir executa o playbook do Ansible para provisionar a imagem.
  4. Executar a função novamente para verificar se ela não muda nada (Idempotence).
  5. Verificar o teste por linting e executá-lo.

A integração de linting no processo de desenvolvimento de script de implementação é bem-vinda. Utilizar ansible-lint, flake8e rubocop ajuda a manter o software de acordo com as melhores práticas ao colaborar. Embora seja possível realizar tudo isso sem o Molecule, é interessante ter algo com essas ferramentas inseridas.

O Idempotence ajuda a assegurar que o script somente faça mudanças que precisem ser feitas. Executar o script duas vezes não deve mudar nada. Isso soa trivial, mas na prática é muito difícil. Utilizar uma tarefa do DB2 ou tarefa de shell para executar ações externas exige uma lógica adicional para impedir a dupla execução.

Para instalar o Molecule, execute o seguinte:

                    pip install molecule

Para criar um projeto de função de estrutura básica usando o Molecule:

ansible-galaxy init ansible-role-dw-bdd-example
cd ansible-role-dw-bdd-example
molecule init
recursos mkdir

O diretório de recursos é para armazenar os arquivos do recurso GHERKIN. Este artigo usa o seguinte exemplo de recurso de segurança:

Recurso:
segurança
História: dados confidenciais do usuário
Como usuário do seu serviço
Eu desejo métodos para que as informações do meu cartão de
crédito protegidas contra roubo
Para que eu não precise cancelar e solicitar um novo cartão.
# https://www.symantec.com/security_response/attacksignatures/detail.jsp?asid=20429
História de má intenção: MyDoom Trojan
Como hacker
Eu desejo infectar um servidor com o MyDoom
Para que eu possa usá-lo como um proxy de soquete seguro para
obter acesso a um sistema História de má intenção: pacotes nginx antigos
Como hacker
Eu desejo aproveitar as vulnerabilidades em pacotes
"desatualizados"
Para que eu possa obter acesso ao sistema Cenário: o proxy
de soquete seguro está bloqueado
  Visto que o servidor tenha um firewall instalado
  Quando uma lista de portas for buscada
  Então a porta de soquete seguro 1080 não será aberta

Estrutura de tópicos do cenário: esperar que as atualizações
de segurança sejam instaladas
   Dado que o servidor seja o Ubuntu 14.04
   E o pacote <package> esteja instalado
   Quando a versão for buscada
   Ela deverá ser igual ou posterior à versão <version>
   Exemplos: pacotes nginx do Ubuntu 14.04 com atualizações de
segurança
     | pacote     | versão          |
     | nginx        | 1.4.6-1ubuntu3.7 |
     | nginx-common | 1.4.6-1ubuntu3.7 |
     | nginx-core   | 1.4.6-1ubuntu3.7 |

Escrevendo as etapas

As definições de etapa são o mecanismo de executar a sintaxe GHERKIN. Embora o GHERKIN seja uma linguagem de programação agnóstica, as definições de etapa são escritas em Python, Ruby, JavaScript e assim por diante. Cada etapa pode usar um ou mais argumentos que podem ser analisados nas instruções da linguagem GHERKIN. Cada instrução no cenário será mapeada para o método nas definições de etapa. Há diferentes implementações do Cucumber para cada linguagem de programação.

Por padrão, o Molecule usa a estrutura de teste TestInfra baseada em Python. A maneira mais fácil de integrar o Cucumber ao Molecule sem usar um executor separado é o TestInfra com pytest-bdd. O motivo para isso é que tanto o TestInfra quanto o pytest-bdd são extensões do pytest Soluções como behave e outras implementações do Cucumber exigem um pouco mais de trabalho para obter a mesma integração. Dito isso, ainda há mais trabalho necessário.

Para fazer com que o TestInfra trabalhe com o Molecule, o objeto do host que estiver usando a API de conexão precisará ser gerado. Como o Molecule gera um inventário instantâneo, inclua isto no início do script ("test/test_default.py"):

import testinfra

host = testinfra.get_host(
 "ansible://all?ansible_inventory=.molecule/ansible_inventory",
 sudo=True)

De pytest-bdd, é necessário ter os pacotes dado, quando, então e os cenários:

from pytest_bdd import (
    given,
    scenarios,
    then,
    when )

Para cada cenário que use umaestrutura de tópicos do cenário, inclua um conversor de exemplo :

@scenario('../features/security.feature',
 'Expect security updates to be installed',
 example_converters=dict(package=str, version=str))
def test_package_scenario():
 '''
 os cenários com tabelas que exigem mapeamento de tipos devem
ser referenciados diretamente antes de chamar "scenarios()"
 '''
 pass

Depois que todos os conversores de exemplo forem incluídos chamando “scenarios('../features’)” para inserir os cenários restantes. O código pytest-bdd usando o objeto do host do TestInfra.

@given('the package <package> is installed')
def package_is_installed(package):
    assert host.package(package).is_installed
    return dict(package=package)


@given('the server is Ubuntu 14.04')
def the_server_is_ubuntu_1404():
    """the server is Ubuntu 14.04."""
    assert host.system_info.type == 'linux'
    assert host.system_info.distribution == 'ubuntu'
    assert host.system_info.release == '14.04'


@when('the server is running')
def the_nginx_server_is_running():
    """the ngingx server is running."""
    assert host.service('nginx').is_running


@when('the version is fetched')
def the_version_is_fetched(package_is_installed):
    """the version is fetched."""
    version = host.package(package_is_installed['package']).version
    package_is_installed['version'] = version


@given('the server has a firewall installed')
def the_server_has_a_firewall_installed():
    """the server has a firewall installed."""
    assert host.package('ufw').is_installed


@pytest.fixture
@when('a list of open ports is fetched')
def a_list_of_open_ports_is_fetched():
    """a list of open ports is fetched."""
    return host.socket.get_listening_sockets()


@then(parsers.parse('the socks port {port:d} is not open'))
def the_socks_port_1080_is_not_open(a_port_scan_is_performed, port):
    """the socks port 1080 is not open."""
    url = 'tcp://0.0.0.0:%d' % port
    assert url not in a_port_scan_is_performed


@then('It should be equal or later than version <version>')
def it_should_be_equal_or_later_than_version_version(package_is_installed,
                                                     version):
    """It should be equal or later than version <version>."""
    assert package_is_installed['version'] == version

Passando pela aprovação dos testes e respondendo a mudanças

Executando O teste molecule deverá falhar porque o script do Ansible ainda não foi escrito. Quando as tarefas do Ansible forem escritas, mais testes deverão ser aprovados até que todos sejam aprovados. Nesse ponto, a função do Ansible estará concluída. Aqui está um conjunto de exemplos de tarefas do Ansible que foram aprovadas nos testes:

---
- name: Install nginx server
  apt:
    name: nginx
- name: Block all ports
  ufw:
    state: enabled
    policy: reject
    log: yes
- name: Allow ssh
  ufw:
    rule: allow
    name: OpenSSH
- name: Allow 443
  ufw:
    rule: allow
    port: 443

A principal vantagem do BDD é que sempre que um teste falha, isso significa que há um defeito ou que a documentação está desatualizada. Por exemplo, se a documentação do recurso for atualizada com uma nova vulnerabilidade de segurança, sua documentação estará atualizada porque os arquivos do recurso são a única fonte verdadeira. Se esse teste falhar, haverá um defeito na configuração do servidor que precisará ser alterada.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=SOA e serviços da web
ArticleID=1051848
ArticleTitle=Um guia do desenvolvedor orientado a comportamento para infraestrutura como código
publish-date=10042017