Friday, February 21, 2020

AWS: API Gateway e Lambda: parte 2

Na primeira parte deste artigo conhecemos o serviço Lambda da AWS. Com o AWS Lambda podemos criar funções que são disparadas por eventos diversos, sendo que toda a implementação de infraestrutura fica a cargo do cloud provider (neste caso AWS), assim podemos focar totalmente no código ao inves de questões relacionadas a servidores, etc. Também criamos uma função simples por linha de comando.


Neste artigo vamos explorar um caso de uso muito comum que é expor a função Lambda através de um recurso REST, para tanto, utilizaremos o serviço AWS API Gateway.

AWS API Gateway

Esse serviço permite criar recursos acessíveis via HTTP que aderem ao prtocolo REST, habilitando comunicação client-server usando os métodos padrões GET, PUT, POST, DELETE e PATCH.

O diagrama abaixo ilustra como a API Gateway pode integrar diversos clientes aos diversos serviços de back-end em um ambiente totalmente serverless:



Chamando a função Lambda via API Gateway

Passo 1: no console da API Gateway, encontre a seção REST API e clique em build:



Passo 2: na página seguinte:
  • embaixo de Choose the Protocol, escolha REST
  • embaixo de Create New API, escolha New API
  • em Settings, dê um nome a sua API (neste caso my-api)



Passso 3: na página seguinte, no botão menu Action, clique em Create Method:



Passo 4: na seção Resources, clique na combo box e escolha GET:



Passo 5: clique no método GET e na seção à direita:
  • em integration type, marque Lambda Function, o que significa que esse recurso irá chamar uma função Lambda
  • marque o check-box Use Lambda Proxy integration. Quando essa opção é marcada, todo envolope HTTP que chega à API Gateway é repassado integralmente à função Lambda sob a forma de um JSON. (Se esta opção não for marcada, o desenvolvedor então deve fazer o mapeamento customizado entre o input da API Gateway e o input da função Lambda)
  • em lambda function, digite o nome da função que será chamada e clique em Save.


Vai aparecer uma pop-up dizendo que será criada uma permissão para este recurso invocar a função Lambda, clique OK.


Passo 6: no botão menu Action clique na opção Deploy API. Vai aparecer uma pop up requisitando um stage para esse deployment (coloque stage ou test):


Em stages, clique no nome do stage que você criou. O metodo já está publicado, repare na invoke url:


Copie e cole essa url em uma nova aba do browser, ele vai chamar a função Lambda e exibir o valor que ela retornar:


Integração entre API Gateway e a função Lambda

Existem duas formas de configurar como a API Gateway vai repassar os dados do request para a função Lambda:


  1. Lambda Proxy Integration: é a opção mais simples, todo envelope HTTP recebido pela API Gateway é convertido em um stream de JSON e repassado integralmente ao Lambda
  2. Lambda Proxy Non-Integration (ou AWS): a função Lambda recebe um input diferente do envelope HTTP (geralmente um objeto do modelo de negócios), neste caso antes de chamar a função lambda, deve-se fazer um mapeamento entre o envelope HTTP e o modelo de dados requerido pela função Lmabda.
Exemplo de uma requisição HTTP no formato JSON repassada integralmente pela API Gateway à função Lambda quando se usa a opção Lambda Proxy Integration:

{
 "resource": "Resource path",
 "path": "Path parameter",
 "httpMethod": "Incoming request's method name"
 "headers": {String containing incoming request headers}
 "multiValueHeaders": {List of strings containing incoming request headers}
 "queryStringParameters": {query string parameters }
 "multiValueQueryStringParameters": {List of query string parameters}
 "pathParameters": {path parameters}
 "stageVariables": {Applicable stage variables}
 "requestContext": {Request context, including authorizer-returned key-value pairs}
 "body": "A JSON string of the request payload."
 "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"
}

"Quem bebe dessa água vai ter sede de novo. Mas aquele que beber da água que eu vou dar, esse nunca mais terá sede. E a água que eu lhe darei, vai se tornar dentro dele uma fonte de água que jorra para a vida eterna."

Jo 4:13-14

       


Tuesday, January 28, 2020

Criptografia e Segurança com Java: parte 1


Criptografia é a prática e o estudo de técnicas que viabilizam uma comunicação segura entre duas partes, através de um canal inseguro (a internet, por exemplo) de modo que seja assegurada a total confidencialidade da mensagem trocada entre as partes, isto é, se algum terceiro interceptar a mensagem, ele não terá condições de entender seu conteúdo, porque a mesma estará criptografada. Caso ele consiga decifrar a mensagem, diz-se que a criptografia foi quebrada.


Em resumo, seguindo a ilustração acima, extraida do livro Understanding Cryptography de Christof Paar e Jan Pelzl, Oscar é um sujeito mau que tentará interceptar a mensagem trocada entre Alice e Bob. Quando se trata de criptografia, temos duas funções:

e(): criptografa (encrypt) o texto x usando a chave k e retorna y (y é x criptografado)

d(): descriptografa (decrypty usando a mesma chave k, de modo que o resultado seja o texto x original

Quando a chave que criptografa e descriptografa são iguais, diz-se que a cripografia é simétrica; quando são diferentes, a criptografia é asimetrica.

Existem dezenas de algoritimos que determinam as etapas para criptografar/descriptografar o texto usando uma determinada chave. Exemplos comuns:

DES: (Data Encryption Standard) durante quase 3 décadas foi o método mais popular de criptografia simétrica. Caiu em desuso no final dos anos 90, quando o barateamento do poder computacional viabilizou a quebra desse método.

RSA: (Rivest–Shamir–Adleman) este algoritmo usa chave asimétrcia, o que significa que a chave que criptografa é diferente da chave que descriptografa. Considerado seguro, é muito usado em autenticações SSH e SSL.

AES: (Advanced Encryption Standard) Largamente aceito hoje, substituiu o DES, tambem é simétrico. AES é o padrão pelo NIST. É usando principalmente em comunicações via HTTPS.

SHA: (Secure Hash Algorithm) Representa uma família de algoritmos (SHA-0, SHA-1, SHA-2, etc.). Transforma o texto usando uma função hash. A criptografia usando SHA só tem um-caminho, ou seja, uma vez criptografada a mensagem, é impossível recuperar a mensagem de volta a partir do hash. Uma aplicação comum do SHA é criptografia de senhas e assinaturas digitais.

MD5: (Message Digest 5) Assim como o SHA, o MD5 é uma função hash de mão única.

A relação completa dos algoritmos suportados pela plataforma Java está na documentação da arquitetura de criptografia do Java.

Exemplo de DES (Data Encryption Standard) em Java

Neste exemplo em java criptografamos uma mensagem utilzando o algoritmo DES, e depois descriptografamos de volta para a original. As classes e interfaces que provem operações de criptografia no java estão no pacote javax.cripto. O pacote java.security também dá suporte para essas operações (as declarações de import estão omitidas).

/**
 * DES (Data Encryption Standard)
 */
public class _01_DES_example {

    //javax.crypto.Cipher provê funcionalidade para Criptografar ou Descriptografar.
    private static Cipher eCipher;
    private static Cipher dCipher;

    // java.security.Key é a interface que representa todos tipos de chave em java
    private static Key secretKey;

    public static void main(String args[])
    {
        try
        {
            // gera uma chave secreta para o algoritmo especificado 'DES'
            secretKey = KeyGenerator.getInstance("DES").generateKey();

            // cria uma instansia de Cipher que irá implementar a transformação da mensagem de acordo com
            // algoritmo especificado, neste caso DES
            eCipher = Cipher.getInstance("DES");
            dCipher = Cipher.getInstance("DES");

            // inicializa este cipher, especificando que ele irá criptografar mensagens
            eCipher.init(Cipher.ENCRYPT_MODE, secretKey);

            // inicializa este cipher, especificando que ele irá DEScriptografar mensagens
            dCipher.init(Cipher.DECRYPT_MODE, secretKey);

            // como a chave que faz a criptografia e a descriptografia é a mesma, então a operação é simétrica

            // mensagem a ser criptografada
            String classifiedMessage = new String("Top secret - This is a classified message!");

            // metodo auxiliar
            String encryptedMsg = criptografar(classifiedMessage);
            System.out.println("\nmenssagem criptografada: "+encryptedMsg);

            // metodo auxiliar
            String decrypted = descriptografar(encryptedMsg);
            System.out.println("\nmensagem descriptografada: "+decrypted);
        }
        // exceções checadas durante o processo de des/criptografia
        catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e)
        {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }
    }

    static String criptografar(String classifiedMessage) throws UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
        // converte a String em bytes
        byte[] utf8 = classifiedMessage.getBytes("UTF-8");

        // criptografa a mensagem
        byte[] mensagemCriptografada = eCipher.doFinal(utf8);

        // converte em base64 para transmissao na rede
        mensagemCriptografada = Base64.getEncoder().encode(mensagemCriptografada);

        return new String(mensagemCriptografada);
    }

    // faz o caminho inverso do metodo criptografar()
    static String descriptografar(String encryptedMessage) throws BadPaddingException, IllegalBlockSizeException {

        // volta para base decimal
        byte[] dec = Base64.getDecoder().decode(encryptedMessage);

        // descriptografa a mensagem
        byte[] utf8 = dCipher.doFinal(dec);

        // retorna a mensagem original
        return new String(utf8);
    }
}

A conversão do texto criptografado em Base64 é necessária para garantir que todos os caracteres da mensagem cheguem integralmente ao destino, uma vez que, sem esse tratamento, durante o tráfego nas diferentes camadas da rede, alguns protocolos ou equipamentos poderiam interpretar errôneamente alguns caracteres e corromper a mensagem.

Resultado da execução do exemplo:

menssagem criptografada: +dIABfsau+f6eq021nwy/dN3ohqDFNcW7Gy6Yfs2PRFV6Fm+vLUc78QTlW8W4m3S

mensagem descriptografada: Top secret - This is a classified message!


"Estejam sempre alegres, rezem sem cessar. Dêem graças em todas as circunstâncias, porque esta é a vontade de Deus a respeito de vocês em Jesus Cristo. Não extingam o Espírito, não desprezem as profecias; examinem tudo e fiquem com o que é bom. Fiquem longe de toda espécie de mal."

1 Ts 5:16-22

s        

Wednesday, January 1, 2020

AWS: API Gateway e Lambda: parte 1

AWS Lambda e API Gateway


API Gateway é um serviço da AWS para criar, publicar, manter e monitorar de maneira muito simples APIs REST ou Web Socket que servem como porta de entrada para serviços que operam no back-end como acesso ao banco de dados, processamento de regras de negócios e diversas outras funcionalidades.

APIs REST são baseadas no protocolo HTTP e implementam os métodos padrões deste protocolo tais como GET, PUT, POST, DELETE e PATCH.

Neste artigo fazemos uma introdução a API Gateway com REST endpoints chamando outro serviço AWS no back-end, neste caso AWS Lambda.

AWS Lambda


Lambda é outro serviço da AWS que permite executar um código baseado em algum evento. O diferencial do Lambda é que ele é sevrerless, ou seja, você apenas cria sua lógica e a AWS cuida de toda infraestrutura necessária como escalonamento, memória, processamento, etc. Lambdas são como funções que rodam na nuvem, cujo código pode ser disparado sob diversas formas.

API Gateway + Lambda

Um caso de uso muito comum é integrar os serviços API Gateway e Lambdas, de modo que a chamada ao primeiro dispare a execução da lógica no segundo. Como exemplo dessa integração:
  1. Criamos uma função Lambda que cria um JSON e o retorna (parte 1)
  2. Um REST endpoint via API Gateway que chama a função e retorna seu resultado (parte 2)
Para executar o exemplo é necessário que você tenha pelo menos uma conta gratuita na AWS e instalar a ferramenta AWS CLI, que permite gerenciar os serviços da AWS por linha de comando.

Criando o Lambda

Utilizando sua IDE de preferencia (neste exemplo uso o INteliJ), crie um projeto MAVEN. O arquivo pom.xml contém as dependencias para se trabalhar com o Lambda e manipulação de JSON com a API gson google:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.finalexception</groupId>
    <artifactId>LambdaApiGatewayP01</artifactId>
    <version>1.0</version>
    <name>lambda-api</name>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

O plugin maven-shade permite compactar todas as dependencias junto com o jar final. Isso será necessário ao subir o arquivo compactado do projeto para executá-lo na nuvem.
Crie um pacote chamado service.lambda e crie classe Function01 cujo código é o que segue:

package service.lambda;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.google.gson.JsonObject;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

public class Function01 implements RequestStreamHandler{

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException
    {
        //API para manipulacao de JSONs
        JsonObject responseJson = new JsonObject();
        JsonObject responseBody = new JsonObject();
        JsonObject headerJson = new JsonObject();

        //propriedade mensagem na resposta
        responseBody.addProperty("message", "it's everything OK here!");

        // um header aleatório na resposta para API Gateway
        headerJson.addProperty("x-custom-header", "my custom header value");

        // Http status code 200 OK
        responseJson.addProperty("statusCode", 200);
        responseJson.add("headers", headerJson);
        responseJson.addProperty("body", responseBody.toString());

        // serializa o JSON para um OutputStream
        OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
        writer.write(responseJson.toString());
        writer.close();
    }
}


A partir da raiz do seu projeto, rode o comando mvn clean package para empacotar o projeto, gerando o arquivo LambdaApiGatewayP01-1.0.jar

Fazendo o deploy do arquivo na AWS

Agora vamos enviar o arquivo do projeto para o serviço AWS Lambda. A partir da raiz do seu projeto, execute o seguinte comando:

aws lambda create-function --function-name funcao01 \
    --zip-file fileb://target/LambdaApiGatewayP01-1.0.jar \
    --handler service.lambda.Function01::handleRequest \
    --runtime java8 \
    --role arn:aws:iam::12121212121:role/developer

O valor do parâmetro --role deve ser qualquer role na sua conta AWS que tenha autorização para criar Lambdas. Quando você cria uma conta, no mínimo deve ter a role administrador, que possui todas as permissões.

Após criar a função, é retornado um JSON contendo várias propriedades da mesma. Observe e copie o valor da propriedade functionArn, representa o endereço dessa função específica no ambiente AWS. Ao invocar essa função, utilizaremos o valor desse functionArn.

Agora que você publicou a função, faça um teste invocando-a. Substitua o valor de functionArn perlo valor gerado na sua conta:

 aws lambda invoke --function-name arn:aws:lambda:us-east-1:205303771310:function:funcao01 \
      --invocation-type RequestResponse /tmp/outfile.txt

O parâmetro /tmp/outfile contém a resposta gerada pela função no formato JSON. Você pode checá-lo abrindo o arquivo:


Na segunda parte intergamos essa função com um endpoint REST via API Gateway. Dessa forma, requisições HTTP no endpoint farão a função disparar.

Referências:
https://aws.amazon.com/lambda/
https://aws.amazon.com/api-gateway/

"Quanto a vocês, não fiquem procurando o que vão de comer e o qque vão beber. Não fiquem inquietos. Porque são os pagãos deste mundo que procuram tudo isso. O Pai bem sabe que vocês têm necessidade dessas coisas. Portanto, busquem o Reino dele, e Deus dará a vocês essas coisas em acréscimo. Não tenha medo, pequeno rebanho, porque o Pai de vocês tem prazer em dar-lhes o Reino"

Lucas 12:29-31