Skip to content

Commit

Permalink
Add API Gateway
Browse files Browse the repository at this point in the history
Use OpenAPI schema to define REST API, then add logging and IAM execution permissions.

Remove lambda function URLs. Test against REST API base URL.

Fixes VEGA-2014 #minor
  • Loading branch information
gregtyler committed Oct 20, 2023
1 parent 1a80069 commit 61a0c61
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 13 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/env-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ on:
description: "Github Token"
required: true
outputs:
create_url:
description: "URL of the create endpoint"
base_url:
description: "Base URL of API"
value: ${{ jobs.terraform_environment_workflow.outputs.url }}

jobs:
Expand Down Expand Up @@ -85,5 +85,5 @@ jobs:
TF_WORKSPACE: ${{ inputs.workspace_name }}
TF_VAR_app_version: ${{ inputs.version_tag }}
run: |
echo "url=$(terraform output -raw lambda_url)" >> $GITHUB_OUTPUT
echo "url=$(terraform output -raw base_url)" >> $GITHUB_OUTPUT
working-directory: ./terraform/environment
2 changes: 2 additions & 0 deletions .github/workflows/env-destroy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ jobs:
working-directory: ./terraform/environment

- name: Destroy deployment environment
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api \
--method DELETE \
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/env-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ name: "[Job] Test environment"
on:
workflow_call:
inputs:
create_url:
description: "URL of the create endpoint"
base_url:
description: "Base URL of API"
required: true
type: string
secrets:
Expand Down Expand Up @@ -48,5 +48,5 @@ jobs:
role-session-name: GitHubActions
- name: POST to server
env:
URL: ${{ inputs.create_url }}
URL: ${{ inputs.base_url }}
run: make test-api
2 changes: 1 addition & 1 deletion .github/workflows/workflow-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
needs: [deploy-pr-env]
uses: ./.github/workflows/env-test.yml
with:
create_url: ${{ needs.deploy-pr-env.outputs.create_url }}
base_url: ${{ needs.deploy-pr-env.outputs.base_url }}
secrets:
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ up:
down:
docker compose down

test-api: URL ?= http://localhost:9000/create
test-api: URL ?= http://localhost:9000/
test-api:
go build -o ./signer/test-api ./signer && \
chmod +x ./signer/test-api && \
./signer/test-api POST $(URL) '{"uid":"M-AL9A-7EY3-075D","version":"1"}'
./signer/test-api PUT $(URL)/M-AL9A-7EY3-075D '{"uid":"M-AL9A-7EY3-075D","version":"1"}'

create-tables:
docker compose run --rm aws dynamodb describe-table --table-name deeds || \
Expand Down
112 changes: 111 additions & 1 deletion docs/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,114 @@ servers:
description: Development
security:
- {}
paths: {}
paths:
/lpas/{uid}:
parameters:
- name: uid
in: path
required: true
description: The UID of the complaint
schema:
type: string
pattern: "M-([A-Z0-9]{4})-([A-Z0-9]{4})-([A-Z0-9]{4})"
example: M-789Q-P4DF-4UX3
put:
operationId: putLpa
summary: Store an LPA
requestBody:
content:
application/json:
schema:
type: object
additionalProperties: false
responses:
"201":
description: Case created
"400":
description: Invalid request
content:
application/json:
schema:
$ref: "#/components/schemas/BadRequestError"
x-amazon-apigateway-auth:
type: "AWS_IAM"
x-amazon-apigateway-integration:
uri: ${lambda_create_invoke_arn}
httpMethod: "POST"
type: "aws_proxy"
contentHandling: "CONVERT_TO_TEXT"
/health:
get:
operationId: healthCheck
summary: Health check endpoint for external services to consume
responses:
200:
description: Healthy
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: OK
additionalProperties: false
503:
description: Service unavailable
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: Unhealthy
additionalProperties: false
x-amazon-apigateway-auth:
type: "AWS_IAM"
x-amazon-apigateway-integration:
type: "mock"
responses:
default:
statusCode: 200
responseTemplates:
application/json: '{"status":"ok", "statusCode":200}'
requestTemplates:
application/json: '{"statusCode": 200}'
passthroughBehavior: "when_no_templates"

components:
schemas:
AbstractError:
type: object
required:
- code
- detail
properties:
code:
type: string
detail:
type: string
BadRequestError:
allOf:
- $ref: "#/components/schemas/AbstractError"
- type: object
properties:
code:
enum: ["INVALID_REQUEST"]
errors:
type: array
items:
type: object
required:
- source
- detail
properties:
source:
type: string
format: jsonpointer
detail:
type: string
example:
- source: "/uid"
detail: "invalid uid format"
2 changes: 1 addition & 1 deletion terraform/environment/.envrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Terraform
export TF_WORKSPACE=development
export TF_WORKSPACE=10vega2104a
export TF_VAR_default_role=operator
export TF_VAR_management_role=operator

Expand Down
5 changes: 5 additions & 0 deletions terraform/environment/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ output "lambda_url" {
description = "Public URL of 'create' Lambda function"
value = module.eu_west_1.lambda_url
}

output "base_url" {
description = "Base URL of API"
value = module.eu_west_1.base_url
}
113 changes: 113 additions & 0 deletions terraform/environment/region/apigateway.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
locals {
stage_name = "current"
template_file = templatefile("../../docs/openapi/openapi.yaml", {
lambda_create_invoke_arn = module.lambda["create"].invoke_arn
})
}

resource "aws_api_gateway_rest_api" "lpa_store" {
name = "lpa-store-${var.environment_name}"
description = "API Gateway for LPA Store - ${var.environment_name}"
body = local.template_file

endpoint_configuration {
types = ["REGIONAL"]
}

provider = aws.region
}


resource "aws_api_gateway_rest_api_policy" "lpa_store" {
rest_api_id = aws_api_gateway_rest_api.lpa_store.id
policy = data.aws_iam_policy_document.lpa_store.json

provider = aws.region
}


resource "aws_api_gateway_deployment" "lpa_store" {
rest_api_id = aws_api_gateway_rest_api.lpa_store.id

triggers = {
redeployment = sha1(jsonencode([
aws_api_gateway_rest_api.lpa_store.body,
var.allowed_arns
]))
}

lifecycle {
create_before_destroy = true
}

depends_on = [
aws_api_gateway_rest_api.lpa_store,
aws_api_gateway_rest_api_policy.lpa_store
]

provider = aws.region
}

resource "aws_api_gateway_stage" "current" {
depends_on = [aws_cloudwatch_log_group.lpa_store]
deployment_id = aws_api_gateway_deployment.lpa_store.id
rest_api_id = aws_api_gateway_rest_api.lpa_store.id
stage_name = local.stage_name
xray_tracing_enabled = true

access_log_settings {
destination_arn = aws_cloudwatch_log_group.lpa_store.arn
format = join("", [
"{\"requestId\":\"$context.requestId\",",
"\"ip\":\"$context.identity.sourceIp\",",
"\"caller\":\"$context.identity.caller\",",
"\"user\":\"$context.identity.user\",",
"\"requestTime\":\"$context.requestTime\",",
"\"httpMethod\":\"$context.httpMethod\"",
"\"resourcePath\":\"$context.resourcePath\",",
"\"status\":\"$context.status\",",
"\"protocol\":\"$context.protocol\",",
"\"responseLength\":\"$context.responseLength\"}"
])
}

provider = aws.region
}

resource "aws_cloudwatch_log_group" "lpa_store" {
name = "API-Gateway-Execution-Logs-${aws_api_gateway_rest_api.lpa_store.name}-${local.stage_name}"
kms_key_id = aws_kms_key.cloudwatch.arn
retention_in_days = 400

provider = aws.region
}

resource "aws_api_gateway_method_settings" "lpa_store_gateway_settings" {
rest_api_id = aws_api_gateway_rest_api.lpa_store.id
stage_name = aws_api_gateway_stage.current.stage_name
method_path = "*/*"

settings {
metrics_enabled = true
logging_level = "INFO"
}

provider = aws.region
}

data "aws_iam_policy_document" "lpa_store" {
policy_id = "lpa-store-${var.environment_name}-${data.aws_region.current.name}-resource-policy"

statement {
sid = "${local.policy_region_prefix}AllowExecutionFromAllowedARNs"
effect = "Allow"

principals {
type = "AWS"
identifiers = var.allowed_arns
}

actions = ["execute-api:Invoke"]
resources = ["*"]
}
}
4 changes: 4 additions & 0 deletions terraform/environment/region/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ output "lambda_url" {
description = "Public URL of 'create' Lambda function"
value = module.lambda["create"].function_url
}

output "base_url" {
value = aws_api_gateway_stage.current.invoke_url
}
9 changes: 9 additions & 0 deletions terraform/environment/region/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ variable "dynamodb_name" {
description = "Name of DynamoDB table"
type = string
}

variable "allowed_arns" {
description = "List of external ARNs allowed to access the API Gateway"
type = list(string)
}

locals {
policy_region_prefix = lower(replace(data.aws_region.current.name, "-", ""))
}
2 changes: 2 additions & 0 deletions terraform/environment/regions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module "eu_west_1" {
dynamodb_arn = aws_dynamodb_table.deeds_table.arn
dynamodb_name = aws_dynamodb_table.deeds_table.name
environment_name = local.environment_name
allowed_arns = local.environment.allowed_arns

providers = {
aws.region = aws.eu_west_1
Expand All @@ -19,6 +20,7 @@ module "eu_west_2" {
dynamodb_arn = aws_dynamodb_table_replica.deeds_table.arn
dynamodb_name = aws_dynamodb_table.deeds_table.name
environment_name = local.environment_name
allowed_arns = local.environment.allowed_arns

providers = {
aws.region = aws.eu_west_2
Expand Down
12 changes: 10 additions & 2 deletions terraform/environment/terraform.tfvars.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@
"default": {
"account_id": "493907465011",
"account_name": "development",
"is_production": false
"is_production": false,
"allowed_arns": [
"arn:aws:iam::493907465011:role/operator",
"arn:aws:iam::493907465011:role/lpa-store-ci"
]
},
"development": {
"account_id": "493907465011",
"account_name": "development",
"is_production": false
"is_production": false,
"allowed_arns": [
"arn:aws:iam::493907465011:role/operator",
"arn:aws:iam::493907465011:role/lpa-store-ci"
]
}
}
}
1 change: 1 addition & 0 deletions terraform/environment/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ variable "environments" {
account_id = string
account_name = string
is_production = bool
allowed_arns = list(string)
})
)
}
Expand Down
5 changes: 5 additions & 0 deletions terraform/modules/lambda/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ output "iam_role_id" {
description = "ID of IAM role created for lambda"
value = aws_iam_role.lambda.id
}

output "invoke_arn" {
description = "value"
value = aws_lambda_function.main.invoke_arn
}

0 comments on commit 61a0c61

Please sign in to comment.