Skip to content

API Gateway Proxy

Introdução

Caso seu objetivo seja boas práticas na hospedagem de um site estático, na qual você quer utilizar o endpoint fornecido da API Gateway (utilizando ela como proxy), esse é o tutorial adequado para você. Vamos utilizar os recursos do terraform para montar nossa infraestrutura.

Observe Abaixo a Arquitetura:

Metodologia

  1. Em primeiro momento, se ainda não clonou, no seu diretório clone o repositório e entre na raiz do repositório. Se clonou, siga para a etapa 2:
    git clone https://github.com/RicardoMourao-py/CloudDefend.git
    
  2. Entre na branch api com o comando abaixo:
    git checkout api
    
  3. Na pasta raíz, exporte as credenciais da sua conta da AWS no terminal:
    export AWS_ACCESS_KEY_ID=<ID_CHAVE_DE_ACESSO>
    export AWS_SECRET_ACCESS_KEY=<CHAVE_SECRETA_DE_ACESSO>
    
  4. Modifique o index.html e o error.html de acordo com o site estático que deseja gerar para o usuário.
  5. Inicie o ambiente Terraform, carregando as dependências necessárias:
    terraform init
    
  6. Aplique e solicite ao terraform que provisione os recursos solicitados na nuvem:
    terraform apply
    
  7. Por fim, visualize se foi criado corretamente a infraestrutura no console da AWS.

Imagens de Verificação

Tela de Início da API

Tela com o Fluxo do Método na Raiz

Tela com o Fluxo do Método do Recurso

Tela Stage do MyS3 com o seu Endpoint

Tela de Política do Bucket integrada a API

Warning

Observe que caso queira acessar o site pelo endpoint fornecido no S3 não é mais possível, e sim apenas pelo endpoint da API.


Código

O código por completo se encontra no arquivo main.tf. As etapas abaixo explicam o passo a passo para cada recurso criado pelo terraform. Vale ressaltar, esta etapa segue este tutorial da aws

Em primeiro momento, é criado o provider da infraestrutura do nosso projeto, responsável por especificar a região onde vamos trabalhar:

main.tf
3
4
5
6
# Provide AWS Credentials
provider "aws" {
  region = "us-east-1"
}

Logo em seguida, há a configuração das políticas necessárias para execução do projeto. É criado uma função do IAM contendo uma política de confiança para que a API Gateway assuma a função. É criado uma política do AWS Identity and Access Management (IAM) com permissão para a API GetObject para o bucket do Amazon S3. É anexado a política de acesso criada anteriormente na role da API Gateway. É criada uma política do bucket para permitir que o proxy da API acesse apenas o bucket do Amazon S3:

main.tf
# Cria função da integração com a política necessária
resource "aws_iam_role" "s3_api_gateyway_role" {
  name = "s3-api-gateyway-role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
} 
  EOF
}

# Anexar política de acesso S3 à função de gateway de API
resource "aws_iam_role_policy_attachment" "s3_policy_attach" {
  role       = "${aws_iam_role.s3_api_gateyway_role.name}"
  policy_arn = "${aws_iam_policy.s3_policy.arn}"  
}

# Create S3 Full Access Policy
resource "aws_iam_policy" "s3_policy" {
  name        = "s3-policy"
  description = "Policy for allowing all S3 Actions"

  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": "*"
        }
    ]
}
EOF
}

# Cria a política do bucket
resource "aws_s3_bucket_policy" "bucket_policy" {
  bucket = aws_s3_bucket.site_bucket.id
  policy = data.aws_iam_policy_document.allow_access_from_another_account.json
}

# cria um IAM para ser referenciado na política do bucket
data "aws_iam_policy_document" "allow_access_from_another_account" {
  version = "2012-10-17"

  statement {
    sid     = "APIProxyBucketPolicy"
    actions = ["s3:GetObject"]
    effect  = "Allow"
    principals {
      type        = "Service"
      identifiers = ["apigateway.amazonaws.com"]
    }
    resources = ["arn:aws:s3:::check-request-2/*"]
    condition {
      test     = "ArnLike"
      variable = "aws:SourceArn"
      values   = ["arn:aws:execute-api:us-east-1:108791993403:${aws_api_gateway_rest_api.MyS3.id}/*/GET/"]
    }
  }
}

Nesta etapa é criado o bucket S3 com todas as configurações necessárias para a hospedagem do site estático com segurança. É criado um bucket S3, com nome check-request-2. Além disso, é habilitado o versionamento o seu versionamento. Por fim, é adicionado e configurado os arquivos index.html e error.html no bucket. Ademais, nesse trecho de código, é configurado os arquivos de índice e de erro. Em seguida, é configurado o acesso público do bucket:

main.tf
resource "aws_s3_bucket" "site_bucket" {
  bucket = "check-request-2"
}

resource "aws_s3_bucket_versioning" "versioning_S3" {
  bucket = aws_s3_bucket.site_bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_object" "index" {
  bucket = aws_s3_bucket.site_bucket.id
  key    = "index.html"
  source = "index.html"
  content_type = "text/html"
}

resource "aws_s3_object" "error" {
  bucket = aws_s3_bucket.site_bucket.id
  key    = "error.html"
  source = "error.html"
  content_type = "text/html"
}


resource "aws_s3_bucket_website_configuration" "site_bucket" {
  bucket = aws_s3_bucket.site_bucket.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}

resource "aws_s3_bucket_public_access_block" "meu_bucket" {
  bucket = aws_s3_bucket.site_bucket.id

  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

Nesta etapa é criada a API Gateway com todas as configurações necessárias para funcionar como proxy. Sendo assim, o client só conseguirá acessar o site com o endpoint da API. Em primeiro momento, é criado a API Gateway com seu respectivo nome. Logo em seguida, é criado um método GET na raiz da API e a sua integração usa como propriedades HTTP_METHOD do tipo GET, tipo AWS Service, conexão com o buclet S3 no arquivo index.html. Além disso, a configuração é finalizada com o arn da role criada na etapa de políticas. É criado o recurso {object} e um método GET dentro do recurso. Ademais, a sua integração segue os mesmos princípios que a etapa anterior, com a única exceção que esse método tem no integration request, a configuração do URL path Parameters. É configurado o Method Response do GET raiz e o GET do object, além de habilitar method.response.header.Content-Type para a próxima etapa. Ademais, é configurado o integration response com os parâmetros necessários para adicionar um valor em Content-Type, criado na etapa anterior, no integration response. Por fim, é feito deploy da aplicação com o nome do estágio MyS3:

main.tf
# Cria API REST
resource "aws_api_gateway_rest_api" "MyS3" {
  name        = "MyS3"
  description = "API for S3 Integration"
}

# Cria o método na raiz
resource "aws_api_gateway_method" "GetBuckets1" {
  rest_api_id   = "${aws_api_gateway_rest_api.MyS3.id}"
  resource_id   = "${aws_api_gateway_rest_api.MyS3.root_resource_id}"
  http_method   = "GET"
  authorization = "NONE"
}

# Etapa para a integração do S3 no GET raiz
resource "aws_api_gateway_integration" "S3Integration1" {
  depends_on = [aws_api_gateway_method.GetBuckets1]
  rest_api_id = "${aws_api_gateway_rest_api.MyS3.id}"
  resource_id = "${aws_api_gateway_rest_api.MyS3.root_resource_id}"
  http_method = "${aws_api_gateway_method.GetBuckets1.http_method}"

  integration_http_method = "GET"

  type = "AWS"
  uri         = "arn:aws:apigateway:us-east-1:s3:path/check-request-2/index.html"

  credentials = "${aws_iam_role.s3_api_gateyway_role.arn}"

}

# Criando recurso
resource "aws_api_gateway_resource" "Object" {
  rest_api_id = "${aws_api_gateway_rest_api.MyS3.id}"
  parent_id   = "${aws_api_gateway_rest_api.MyS3.root_resource_id}"
  path_part   = "{object}"
}

# Cria o método no recurso
resource "aws_api_gateway_method" "GetBuckets2" {
  rest_api_id   = "${aws_api_gateway_rest_api.MyS3.id}"
  resource_id   = "${aws_api_gateway_resource.Object.id}"
  http_method   = "GET"
  authorization = "NONE"

  request_parameters = {
    "method.request.path.object" = true
  }
}

# Etapa para a integração do S3 no GET do object
resource "aws_api_gateway_integration" "S3Integration2" {
  rest_api_id = "${aws_api_gateway_rest_api.MyS3.id}"
  resource_id = "${aws_api_gateway_resource.Object.id}"
  http_method = "${aws_api_gateway_method.GetBuckets2.http_method}"

  integration_http_method = "GET"

  type = "AWS"
  uri         = "arn:aws:apigateway:us-east-1:s3:path/check-request-2/{object}"

  credentials = "${aws_iam_role.s3_api_gateyway_role.arn}"

  request_parameters = {
      "integration.request.path.object" = "method.request.path.object"
    }
}

resource "aws_api_gateway_integration_response" "MyS3IntegrationResponse" {
  depends_on = [aws_api_gateway_integration.S3Integration1]

  rest_api_id = "${aws_api_gateway_rest_api.MyS3.id}"
  resource_id = "${aws_api_gateway_rest_api.MyS3.root_resource_id}"
  http_method = "${aws_api_gateway_method.GetBuckets1.http_method}"
  status_code       = "200"  

  response_parameters = {
    "method.response.header.Content-Type"   = "integration.response.header.Content-Type"
  }

}

resource "aws_api_gateway_integration_response" "MyS3IntegrationResponse_object" {
  depends_on = [aws_api_gateway_integration.S3Integration2]

  rest_api_id = "${aws_api_gateway_rest_api.MyS3.id}"
  resource_id = "${aws_api_gateway_resource.Object.id}"
  http_method = "${aws_api_gateway_method.GetBuckets2.http_method}"
  status_code       = "200"  

  response_parameters = {
    "method.response.header.Content-Type"   = "integration.response.header.Content-Type"
  }
}

resource "aws_api_gateway_method_response" "response_200" {
  rest_api_id = aws_api_gateway_rest_api.MyS3.id
  resource_id = aws_api_gateway_rest_api.MyS3.root_resource_id
  http_method = aws_api_gateway_method.GetBuckets1.http_method
  status_code = "200"

  response_parameters = {
    "method.response.header.Content-Type" = true
  }
}

resource "aws_api_gateway_method_response" "response_200_object" {
  rest_api_id = aws_api_gateway_rest_api.MyS3.id
  resource_id = aws_api_gateway_resource.Object.id
  http_method = aws_api_gateway_method.GetBuckets2.http_method
  status_code = "200"

  response_parameters = {
    "method.response.header.Content-Type" = true
  }
}

resource "aws_api_gateway_deployment" "S3APIDeployment" {
  depends_on  = [aws_api_gateway_method.GetBuckets1,
                 aws_api_gateway_method.GetBuckets2, aws_api_gateway_method_response.response_200_object,
                 aws_api_gateway_method_response.response_200, aws_api_gateway_integration_response.MyS3IntegrationResponse_object,
                 aws_api_gateway_integration_response.MyS3IntegrationResponse, aws_api_gateway_integration.S3Integration1,
                 aws_api_gateway_integration.S3Integration2, aws_api_gateway_resource.Object
                ]
  rest_api_id = "${aws_api_gateway_rest_api.MyS3.id}"
}

resource "aws_api_gateway_stage" "MyS3stage" {
  depends_on  = [aws_api_gateway_method.GetBuckets1, aws_api_gateway_deployment.S3APIDeployment,
                 aws_api_gateway_method.GetBuckets2, aws_api_gateway_method_response.response_200_object,
                 aws_api_gateway_method_response.response_200, aws_api_gateway_integration_response.MyS3IntegrationResponse_object,
                 aws_api_gateway_integration_response.MyS3IntegrationResponse, aws_api_gateway_integration.S3Integration1,
                 aws_api_gateway_integration.S3Integration2, aws_api_gateway_resource.Object
                ]
  stage_name      = "MyS3"
  rest_api_id     = aws_api_gateway_rest_api.MyS3.id
  deployment_id   = aws_api_gateway_deployment.S3APIDeployment.id
}