diff --git a/.github/workflows/lambda-build.yml b/.github/workflows/lambda-build.yml index 44238b9..5de1132 100644 --- a/.github/workflows/lambda-build.yml +++ b/.github/workflows/lambda-build.yml @@ -5,8 +5,8 @@ name: Lambda Build # Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the master branch - pull_request: - branches: [ master ] + # pull_request: + # branches: [ master ] # Allows you to run this workflow manually from the Actions tab diff --git a/.gitignore b/.gitignore index 1e31ae0..802f342 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,14 @@ notes pgmSSHKey.pem *.iml -scratch/ \ No newline at end of file +scratch/ +**/.terraform/* +*.tfstate +*.tfstate.* +crash.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.DS_Store +*.DS_Store* \ No newline at end of file diff --git a/tf/locals.tf b/tf/locals.tf index 206fc35..94eeecf 100644 --- a/tf/locals.tf +++ b/tf/locals.tf @@ -67,12 +67,12 @@ data "template_file" "api_swagger" { template = file("modules/lambda/tf-swagger-userapi-v3.yaml") vars = { - lambda_arn = module.lambda.lambda_arn + lambda_arn = module.lambda.lambda_arn ddb_action_get_item = "arn:aws:apigateway:us-east-1:dynamodb:action/GetItem" - ddb_action_scan = "arn:aws:apigateway:us-east-1:dynamodb:action/Scan" - ddb_action_query = "arn:aws:apigateway:us-east-1:dynamodb:action/Query" - ddb_role_arn = module.ddb-extusers.iam-access-ddb-role-arn - users_table_name = module.ddb-extusers.ddb-extusers-name + ddb_action_scan = "arn:aws:apigateway:us-east-1:dynamodb:action/Scan" + ddb_action_query = "arn:aws:apigateway:us-east-1:dynamodb:action/Query" + ddb_role_arn = module.ddb-extusers.iam-access-ddb-role-arn + users_table_name = module.ddb-extusers.ddb-extusers-name } } diff --git a/tf/main.tf b/tf/main.tf index fd6b838..357e3ac 100644 --- a/tf/main.tf +++ b/tf/main.tf @@ -1,28 +1,30 @@ -module "ddb-extusers" { - source = "./modules/ddb-extusers" - env = var.env +module "dynamodb" { + source = "./modules/dynamodb" + env = var.env } module "lambda" { - depends_on = [module.ddb-extusers] - source = "./modules/lambda" - env = var.env + depends_on = [module.dynamodb] + + source = "./modules/lambda" + env = var.env must-be-role-prefix = var.role-prefix - must-be-policy-arn = var.policy-boundary-arn - ddb-table-name = module.ddb-extusers.ddb-extusers-name + must-be-policy-arn = var.policy-boundary-arn + ddb-table-name = module.dynamodb.dynamodb_table_name } module "api-gateway" { - depends_on = [module.ddb-extusers, module.lambda] - source = "./modules/api-gateway" - env = var.env + depends_on = [module.dynamodb, module.lambda] + + source = "./modules/api-gateway" + env = var.env must-be-role-prefix = var.role-prefix - must-be-policy-arn = var.policy-boundary-arn - okta-issuer = lookup(local.OktaMap, var.env).issuer - app-name = "eracommons" - app-description = "Enterprise Data & Integration Services Web Services" - api-swagger = data.template_file.api_swagger.rendered - api-resource-policy = local.resource_policy + must-be-policy-arn = var.policy-boundary-arn + okta-issuer = lookup(local.OktaMap, var.env).issuer + app-name = "eracommons" + app-description = "Enterprise Data & Integration Services Web Services" + api-swagger = data.template_file.api_swagger.rendered + api-resource-policy = local.resource_policy } diff --git a/tf/modules/api-gateway/README.md b/tf/modules/api-gateway/README.md index 365e01b..386658c 100644 --- a/tf/modules/api-gateway/README.md +++ b/tf/modules/api-gateway/README.md @@ -1,3 +1,6 @@ +The api-gateway module creates api gateway based on a given swagger file and includes lambda authorizer for authorization API calls + + ## Requirements diff --git a/tf/modules/api-gateway/data.tf b/tf/modules/api-gateway/data.tf new file mode 100644 index 0000000..860a54c --- /dev/null +++ b/tf/modules/api-gateway/data.tf @@ -0,0 +1,23 @@ +data "aws_iam_policy_document" "assume_role_lambda_service" { + statement { + sid = "" + effect = "Allow" + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "assume_role_apigateway_service" { + statement { + sid = "" + effect = "Allow" + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["apigateway.amazonaws.com"] + } + } +} \ No newline at end of file diff --git a/tf/modules/api-gateway/main.tf b/tf/modules/api-gateway/main.tf index 109c64e..2a5195f 100644 --- a/tf/modules/api-gateway/main.tf +++ b/tf/modules/api-gateway/main.tf @@ -1,101 +1,140 @@ -# -# The api-gateway module creates api gateway based on a given swagger file -# and includes lambda authorizer for authorization API calls -# +resource "aws_api_gateway_account" "api_gateway" { + cloudwatch_role_arn = aws_iam_role.api_gateway.arn +} + +resource "aws_api_gateway_rest_api" "api_gateway" { + name = "${var.app}-REST-API-${var.env}" + description = "${var.env} - ${var.app-description}" + disable_execute_api_endpoint = var.apigw_disable_execute_api_endpoint + body = [var.api-swagger] + + endpoint_configuration { + types = var.apigw_endpoint_config + } + + tags = { + app = var.app + } +} + +# the policy would ideally be set-up with a datasource (and lifecycle hook for depends on) +# can still use count with a bool variable to indicate whether or not a policy should be applied. +resource "aws_api_gateway_rest_api_policy" "api_gateway" { + count = (var.api-resource-policy != "") ? 1 : 0 + + rest_api_id = aws_api_gateway_rest_api.api_gateway.id + policy = var.api-resource-policy +} + +resource "aws_api_gateway_deployment" "api_gateway" { + rest_api_id = aws_api_gateway_rest_api.api_gateway.id +} + +resource "aws_api_gateway_stage" "api_gateway" { + deployment_id = aws_api_gateway_deployment.api_gateway.id + rest_api_id = aws_api_gateway_rest_api.api_gateway.id + stage_name = var.env + cache_cluster_enabled = var.apigw_stage_cache_enabled + cache_cluster_size = var.apigw_stage_cache_size + xray_tracing_enabled = var.apigw_stage_xray_enabled + + access_log_settings { + destination_arn = aws_cloudwatch_log_group.api_gateway.arn + format = "{ \"requestTime\": \"$context.requestTime\", \"requestId\": \"$context.requestId\", \"httpMethod\": \"$context.httpMethod\", \"path\": \"$context.path\", \"resourcePath\": \"$context.resourcePath\", \"status\": $context.status, \"responseLatency\": $context.responseLatency, \"xrayTraceId\": \"$context.xrayTraceId\", \"integrationRequestId\": \"$context.integration.requestId\", \"functionResponseStatus\": \"$context.integration.status\", \"integrationLatency\": \"$context.integration.latency\", \"integrationServiceStatus\": \"$context.integration.integrationStatus\", \"authorizeResultStatus\": \"$context.authorize.status\", \"authorizerServiceStatus\": \"$context.authorizer.status\", \"authorizerLatency\": \"$context.authorizer.latency\", \"authorizerRequestId\": \"$context.authorizer.requestId\", \"ip\": \"$context.identity.sourceIp\", \"userAgent\": \"$context.identity.userAgent\", \"principalId\": \"$context.authorizer.principalId\", \"user\": \"$context.identity.user\" }" + } +} +resource "aws_api_gateway_method_settings" "api_gateway" { + method_path = var.apigw_method_path + rest_api_id = aws_api_gateway_rest_api.api_gateway.id + stage_name = aws_api_gateway_stage.api_gateway.stage_name + + settings { + metrics_enabled = var.apigw_method_metrics_enabled + logging_level = var.apigw_method_log_level + data_trace_enabled = var.apigw_method_trace_enabled + cache_data_encrypted = var.apigw_method_cache_encryption + cache_ttl_in_seconds = var.apigw_method_cache_ttl + caching_enabled = var.apigw_method_cache_enabled + } +} # Lambda Authorizer + +resource "aws_api_gateway_authorizer" "api_gateway" { + name = "${var.app}-lambda-authorizer-${var.env}" + rest_api_id = aws_api_gateway_rest_api.api_gateway.id + authorizer_uri = aws_lambda_function.auth_lambda.invoke_arn + type = var.authorizer_type +} + resource "aws_lambda_function" "auth_lambda" { - function_name = "lambda-auth-${var.env}" + function_name = "${var.app}-lambda-auth-${var.env}" role = aws_iam_role.auth_lambda.arn description = "Lambda function with basic authorization." - handler = "src/lambda.handler" - runtime = "nodejs12.x" - memory_size = 2048 - timeout = 30 + handler = var.lambda_handler_file + runtime = var.lambda_runtime + memory_size = var.lambda_memory_size + timeout = var.timeout + filename = var.lambda_file_location + tracing_config { - mode = "Active" + mode = var.lambda_tracing_mode } + environment { variables = { - "LOG_LEVEL" = "info" + "LOG_LEVEL" = var.lambda_log_level "AUDIENCE" = var.okta-audience "ISSUER" = var.okta-issuer } } + tags = { - app = var.app-name + app = var.app } - filename = "../lambda-zip/lambda-auth/lambda-auth.zip" } resource "aws_lambda_function_event_invoke_config" "auth_lambda" { - function_name = aws_lambda_function.auth_lambda.function_name - maximum_retry_attempts = 0 -} - -resource "aws_api_gateway_account" "api_gateway" { - cloudwatch_role_arn = "${aws_iam_role.api_gateway.arn}" -} - -resource "aws_api_gateway_rest_api" "api_gateway" { - name = "${var.app-name} - ${var.env}" - description = "${var.env} - ${var.app-description}" - endpoint_configuration { - types = ["REGIONAL"] - } - body = var.api-swagger + function_name = aws_lambda_function.auth_lambda.function_name + maximum_retry_attempts = var.lambda_config_retry_attempts } -resource "aws_api_gateway_deployment" "api_gateway" { - rest_api_id = "${aws_api_gateway_rest_api.api_gateway.id}" +resource "aws_cloudwatch_log_group" "api_gateway" { + name = "${var.portfolio}-${var.env}-${var.app}-accesslogs" + retention_in_days = var.cloudwatch_log_retention_days } -resource "aws_api_gateway_authorizer" "api_gateway" { - name = "era-commons-user-api-authorizer" - rest_api_id = "${aws_api_gateway_rest_api.api_gateway.id}" - authorizer_uri = "${aws_lambda_function.auth_lambda.invoke_arn}" - type = "TOKEN" +resource "aws_iam_role" "auth_lambda" { + name = "${var.must-be-role-prefix}-lambda-auth-${var.env}" + assume_role_policy = data.aws_iam_policy_document.assume_role_lambda_service.json + path = "/" + permissions_boundary = var.must-be-policy-arn } -resource "aws_api_gateway_rest_api_policy" "api_gateway" { - count = (var.api-resource-policy != "") ? 1 : 0 - rest_api_id = aws_api_gateway_rest_api.api_gateway.id - policy = var.api-resource-policy +resource "aws_iam_role_policy_attachment" "auth_lambda" { + role = aws_iam_role.auth_lambda.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } -resource "aws_cloudwatch_log_group" "api_gateway" { - name = "business_apps-${var.env}-${var.app-name}-accesslogs" - retention_in_days = 90 +resource "aws_iam_role_policy_attachment" "auth_lambda" { + role = aws_iam_role.auth_lambda.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" } -resource "aws_api_gateway_stage" "api_gateway" { - deployment_id = "${aws_api_gateway_deployment.api_gateway.id}" - rest_api_id = "${aws_api_gateway_rest_api.api_gateway.id}" - stage_name = "${var.env}" - cache_cluster_enabled = true - cache_cluster_size = "1.6" - access_log_settings { - destination_arn = "${aws_cloudwatch_log_group.api_gateway.arn}" - format = "{ \"requestTime\": \"$context.requestTime\", \"requestId\": \"$context.requestId\", \"httpMethod\": \"$context.httpMethod\", \"path\": \"$context.path\", \"resourcePath\": \"$context.resourcePath\", \"status\": $context.status, \"responseLatency\": $context.responseLatency, \"xrayTraceId\": \"$context.xrayTraceId\", \"integrationRequestId\": \"$context.integration.requestId\", \"functionResponseStatus\": \"$context.integration.status\", \"integrationLatency\": \"$context.integration.latency\", \"integrationServiceStatus\": \"$context.integration.integrationStatus\", \"authorizeResultStatus\": \"$context.authorize.status\", \"authorizerServiceStatus\": \"$context.authorizer.status\", \"authorizerLatency\": \"$context.authorizer.latency\", \"authorizerRequestId\": \"$context.authorizer.requestId\", \"ip\": \"$context.identity.sourceIp\", \"userAgent\": \"$context.identity.userAgent\", \"principalId\": \"$context.authorizer.principalId\", \"user\": \"$context.identity.user\" }" - } - xray_tracing_enabled = true +resource "aws_iam_role_policy_attachment" "auth_lambda" { + role = aws_iam_role.auth_lambda.name + policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess" } -resource "aws_api_gateway_method_settings" "api_gateway" { - method_path = "*/*" - rest_api_id = "${aws_api_gateway_rest_api.api_gateway.id}" - stage_name = "${aws_api_gateway_stage.api_gateway.stage_name}" - settings { - metrics_enabled = true - logging_level = "INFO" - data_trace_enabled = true - cache_data_encrypted = true - cache_ttl_in_seconds = 300 - caching_enabled = true - } +resource "aws_iam_role" "api_gateway" { + name = "${var.must-be-role-prefix}-api-gateway-${var.app}-${var.env}" + assume_role_policy = data.aws_iam_policy_document.assume_role_apigateway_service + path = "/" + permissions_boundary = var.must-be-policy-arn } -output "url" { - value = "${aws_api_gateway_deployment.api_gateway.invoke_url}/api" -} +resource "aws_iam_role_policy_attachment" "api_gateway" { + role = aws_iam_role.api_gateway.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" +} \ No newline at end of file diff --git a/tf/modules/api-gateway/outputs.tf b/tf/modules/api-gateway/outputs.tf new file mode 100644 index 0000000..29a8b2e --- /dev/null +++ b/tf/modules/api-gateway/outputs.tf @@ -0,0 +1,3 @@ +output "url" { + value = "${aws_api_gateway_deployment.api_gateway.invoke_url}/api" +} \ No newline at end of file diff --git a/tf/modules/api-gateway/roles.tf b/tf/modules/api-gateway/roles.tf deleted file mode 100644 index 53ca36d..0000000 --- a/tf/modules/api-gateway/roles.tf +++ /dev/null @@ -1,50 +0,0 @@ -# -# IAM roles for Lambda Authorizer and API Gateway -# - -resource "aws_iam_role" "auth_lambda" { - name = "${var.must-be-role-prefix}-lambda-auth-${var.env}" - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Effect = "Allow" - Sid = "" - Principal = { - Service = "lambda.amazonaws.com" - } - } - ] - }) - managed_policy_arns = [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", - "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess" - ] - path = "/" - permissions_boundary = var.must-be-policy-arn -} - -resource "aws_iam_role" "api_gateway" { - name = "${var.must-be-role-prefix}-api-gateway-user-api-${var.env}" - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Effect = "Allow" - Sid = "" - Principal = { - Service = "apigateway.amazonaws.com" - } - } - ] - }) - managed_policy_arns = [ - "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" - ] - path = "/" - permissions_boundary = var.must-be-policy-arn -} - diff --git a/tf/modules/api-gateway/variables.tf b/tf/modules/api-gateway/variables.tf index ac1b975..545c2cc 100644 --- a/tf/modules/api-gateway/variables.tf +++ b/tf/modules/api-gateway/variables.tf @@ -1,42 +1,161 @@ +#General Variables +variable "portfolio" { + type = string + description = "The name of the portfolio to which the appliation/service belongs (examples include busiess_apps, scientific, grants, etc.)" +} + +variable "app" { + type = string + description = "The name of the application that this IaC supports" +} + variable "env" { - default = "" description = "The deployment tier (dev/test/qa/stage/prod and others)" } + variable "must-be-role-prefix" { - default = "" + default = "" description = "Mandatory IAM role name prefix" } + variable "must-be-policy-arn" { - default = "" + default = "" description = "Mandatory policy to be included in any IAM role" } +variable "app-description" { + default = "" + description = "Description of API Gateway project" +} + +variable "lambda_handler_file" { + type = string + description = "The source location for the lambda handler definition (i.e. src/handler.lambda)" +} + +variable "lambda_runtime" { + type = string + description = "The runtime for the lambda function. The default value represents the latest runtime version validated for this configuration" + default = "nodejs12.x" +} + +variable "lambda_config_retry_attempts" { + type = number + description = "Maximum number of times to retry when the function returns an error. Valid values between 0 and 2." + default = 0 +} + +variable "lambda_log_level" { + type = string + description = "Set the log level for your Lambda Function" + default = "info" +} + +variable "lambda_file_location" { + type = string + description = "Path to the function's deployment package within the local filesystem (i.e. '../lambda-zip/lambda-userapi/lambda-userapi.zip')" +} + variable "okta-issuer" { - default = "" + default = "" description = "URL to OKTA provider authentication server" } variable "okta-audience" { - default = "api://default" + default = "api://default" description = "AUDIENCE for OKTA provider authentication server" } -variable "app-name" { - default = "apigateway" - description = "Name of the project that will be assigned as a tag to every resource of the project, also used in API Gateway API name" -} - -variable "app-description" { - default = "" - description = "Description of API Gateway project" +#API Gateway Variables +variable "apigw_endpoint_config" { + type = string + description = "" } variable "api-swagger" { - default = "" + default = "" description = "The rendered OpenAPI specification that defines the set of routes and integrations to create as part of the REST API." } +variable "apigw_disable_execute_api_endpoint" { + type = bool + description = "Specifies whether clients can invoke your API by using the default execute-api endpoint. By default, clients can invoke your API with the default https://{api_id}.execute-api.{region}.amazonaws.com endpoint. To require that clients use a custom domain name to invoke your API, disable the default endpoint. Defaults to false. If importing an OpenAPI specification via the body argument, this corresponds to the x-amazon-apigateway-endpoint-configuration extension disableExecuteApiEndpoint property. If the argument value is true and is different than the OpenAPI value, the argument value will override the OpenAPI value." + default = false +} + +variable "authorizer_type" { + type = string + description = "The type of the authorizer. Possible values are TOKEN for a Lambda function using a single authorization token submitted in a custom header, REQUEST for a Lambda function using incoming request parameters, or COGNITO_USER_POOLS for using an Amazon Cognito user pool." + default = "TOKEN" +} + variable "api-resource-policy" { - default = "" + default = "" description = "Optional resource policy to be applied to api gateway" } + +#API Gateway Stage Variables +variable "apigw_stage_cache_enabled" { + type = bool + description = "Set to true to enable an API Gateway cache" + default = false +} + +variable "apigw_stage_cache_size" { + type = string + description = " The size of the cache cluster for the stage (in GiB), if enabled. Allowed values include 0.5, 1.6, 6.1, 13.5, 28.4, 58.2, 118 and 237." +} + +variable "apigw_stage_xray_enabled" { + type = bool + description = "" + default = false +} + +# API Gateway Method Variables +variable "apigw_method_cache_enabled" { + type = bool + description = "" + default = false +} + +variable "apigw_method_path" { + type = string + description = "" + default = "*/*" +} + +variable "apigw_method_log_level" { + type = string + description = "" + default = "info" +} + +variable "apigw_method_cache_encryption" { + type = bool + description = "" +} + +variable "apigw_method_cache_ttl" { + type = number + description = "" + default = 300 +} + +variable "apigw_method_metrics_enabled" { + type = bool + description = "" + default = true +} + +variable "apigw_method_trace_enabled" { + type = bool + description = "" +} + +# CloudWatch Variables +variable "cloudwatch_log_retention_days" { + type = number + description = "The number of days that logs within a log group will be retained." + default = 90 +} diff --git a/tf/modules/ddb-extusers/main.tf b/tf/modules/ddb-extusers/main.tf deleted file mode 100644 index 255c6cf..0000000 --- a/tf/modules/ddb-extusers/main.tf +++ /dev/null @@ -1,52 +0,0 @@ - -resource "aws_dynamodb_table" "extusers-table" { - name = "extusers-${var.env}" - hash_key = "USER_ID" - billing_mode = "PROVISIONED" - read_capacity = 5 - write_capacity = 5 - - - attribute { - name = "USER_ID" - type = "S" - } - attribute { - name = "LAST_UPDATED_DAY" - type = "S" - } - attribute { - name = "LOGINGOV_USER_ID" - type = "S" - } - - global_secondary_index { - hash_key = "LAST_UPDATED_DAY" - range_key = "USER_ID" - name = "dateIndex" - projection_type = "ALL" - read_capacity = 5 - write_capacity = 5 - } - - global_secondary_index { - hash_key = "LOGINGOV_USER_ID" - range_key = "USER_ID" - name = "logingovIndex" - projection_type = "ALL" - read_capacity = 5 - write_capacity = 5 - } -} - -output "ddb-extusers-arn" { - value = aws_dynamodb_table.extusers-table.arn -} - -output "ddb-extusers-name" { - value = aws_dynamodb_table.extusers-table.name -} - -output "iam-access-ddb-role-arn" { - value = aws_iam_role.iam_access_ddb.arn -} \ No newline at end of file diff --git a/tf/modules/ddb-extusers/roles.tf b/tf/modules/ddb-extusers/roles.tf deleted file mode 100644 index fc0c8f2..0000000 --- a/tf/modules/ddb-extusers/roles.tf +++ /dev/null @@ -1,42 +0,0 @@ -resource "aws_iam_policy" "iam_access_ddb" { - name = "${var.must-be-role-prefix}-ddb-extusers-read-${var.env}" - path = "/" - description = "Access to given ddb table" - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = ["dynamodb:*"] - Effect = "Allow" - Sid = "ddbPermissions" - Resource = [ - aws_dynamodb_table.extusers-table.arn, - "${aws_dynamodb_table.extusers-table.arn}/index/*" - ] - } - ] - }) -} - -resource "aws_iam_role" "iam_access_ddb" { - name = "${var.must-be-role-prefix}-api-gateway-user-api-ddb-${var.env}" - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Effect = "Allow" - Sid = "" - Principal = { - Service = "apigateway.amazonaws.com" - } - } - ] - }) - managed_policy_arns = [ - "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs", - aws_iam_policy.iam_access_ddb.arn - ] - path = "/" - permissions_boundary = var.must-be-policy-arn -} diff --git a/tf/modules/ddb-extusers/variables.tf b/tf/modules/ddb-extusers/variables.tf deleted file mode 100644 index c861034..0000000 --- a/tf/modules/ddb-extusers/variables.tf +++ /dev/null @@ -1,13 +0,0 @@ -variable "env" { - default = "" -} - -variable "must-be-role-prefix" { - default = "" - description = "Mandatory IAM role name prefix" -} -variable "must-be-policy-arn" { - default = "" - description = "Mandatory policy to be included in any IAM role" -} - diff --git a/tf/modules/ddb-extusers/README.md b/tf/modules/dynamodb/README.md similarity index 100% rename from tf/modules/ddb-extusers/README.md rename to tf/modules/dynamodb/README.md diff --git a/tf/modules/dynamodb/data.tf b/tf/modules/dynamodb/data.tf new file mode 100644 index 0000000..2a1326f --- /dev/null +++ b/tf/modules/dynamodb/data.tf @@ -0,0 +1,65 @@ +data "aws_iam_policy_document" "dynamodb_assume_role" { + statement { + sid = "DynamoAssumeRoleAPIGW" + effect = "Allow" + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["apigateway.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "dynamodb_access" { + statement { + sid = "DynamoDbAccess" + effect = "Allow" + actions = [ + "dynamodb:BatchGetItem", + "dynamodb:BatchWriteItem", + "dynamodb:ConditionCheckItem", + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + "dynamodb:DescribeTimeToLive", + "dynamodb:GetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:ListStreams", + "dynamodb:ListTables", + "dynamodb:ListTagsOfResource", + "dynamodb:PartiQLDelete", + "dynamodb:PartiQLInsert", + "dynamodb:PartiQLSelect", + "dynamodb:PartiQLUpdate", + "dynamodb:PutItem", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:TagResource", + "dynamodb:UntagResource", + "dynamodb:UpdateItem", + "dynamodb:UpdateTable", + "dynamodb:UpdateTimeToLive" + ] + resources = [ + aws_dynamodb_table.dynamodb.arn, + "${aws_dynamodb_table.dynamodb.arn}/index/*", + "${aws_dynamodb_table.dynamodb.arn}/stream/*" + ] + } +} + + +policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = ["dynamodb:*"] + Effect = "Allow" + Sid = "ddbPermissions" + Resource = [ + aws_dynamodb_table.dynamodb.arn, + "${aws_dynamodb_table.dynamodb.arn}/index/*" + ] + } + ] +}) \ No newline at end of file diff --git a/tf/modules/dynamodb/main.tf b/tf/modules/dynamodb/main.tf new file mode 100644 index 0000000..15ab225 --- /dev/null +++ b/tf/modules/dynamodb/main.tf @@ -0,0 +1,56 @@ + +resource "aws_dynamodb_table" "dynamodb" { + name = "${var.app}-dynamodb-table-${var.env}" + hash_key = var.dynamodb_hash_key + billing_mode = var.dynamodb_billing_mode + read_capacity = var.dynamodb_read_capacity + write_capacity = var.dynamodb_write_capacity + stream_enabled = var.dynamodb_stream_enabled + stream_view_type = var.dynamodb_stream_view_type + table_class = var.dynamodb_table_class + + ttl { + enabled = var.dynamodb_ttl_enabled + attribute_name = "ttl" + } + + attribute { + name = "USER_ID" + type = "S" + } + attribute { + name = "LAST_UPDATED_DAY" + type = "S" + } + attribute { + name = "LOGINGOV_USER_ID" + type = "S" + } + + global_secondary_index { + hash_key = "LAST_UPDATED_DAY" + range_key = "USER_ID" + name = "dateIndex" + projection_type = "ALL" + read_capacity = 5 + write_capacity = 5 + } + + global_secondary_index { + hash_key = "LOGINGOV_USER_ID" + range_key = "USER_ID" + name = "logingovIndex" + projection_type = "ALL" + read_capacity = 5 + write_capacity = 5 + } + + lifecycle { + create_before_destroy = true + } + + tags = { + app = var.app + tier = var.env + } +} \ No newline at end of file diff --git a/tf/modules/dynamodb/outputs.tf b/tf/modules/dynamodb/outputs.tf new file mode 100644 index 0000000..9b2f40c --- /dev/null +++ b/tf/modules/dynamodb/outputs.tf @@ -0,0 +1,7 @@ +output "dynamodb_arn" { + value = aws_dynamodb_table.dynamodb.arn +} + +output "dynamodb_table_name" { + value = aws_dynamodb_table.dynamodb.name +} \ No newline at end of file diff --git a/tf/modules/dynamodb/variables.tf b/tf/modules/dynamodb/variables.tf new file mode 100644 index 0000000..8810a96 --- /dev/null +++ b/tf/modules/dynamodb/variables.tf @@ -0,0 +1,57 @@ +variable "env" { + type = string +} + +variable "app" { + type = string +} + +variable "dynamodb_hash_key" { + type = string +} + +variable "dynamodb_billing_mode" { + type = string +} + +variable "dynamodb_read_capacity" { + type = number + default = 5 +} + +variable "dynamodb_write_capacity" { + type = number + default = 5 +} + +variable "dynamodb_ttl_enabled" { + type = bool + description = "" + default = false +} + +variable "dynamodb_stream_enabled" { + type = bool + default = false +} + +variable "dynamodb_stream_view_type" { + type = string + description = "When an item in the table is modified, StreamViewType determines what information is written to the table's stream. Valid values are KEYS_ONLY, NEW_IMAGE, OLD_IMAGE, NEW_AND_OLD_IMAGES." +} + +variable "dynamodb_table_class" { + type = string + description = "The storage class of the table. Valid values are STANDARD and STANDARD_INFREQUENT_ACCESS." + default = "STANDARD" +} + +variable "must-be-role-prefix" { + type = string + description = "Mandatory IAM role name prefix" +} +variable "must-be-policy-arn" { + type = string + description = "Mandatory policy to be included in any IAM role" +} + diff --git a/tf/modules/lambda/main.tf b/tf/modules/lambda/main.tf index 682a2ae..df7dc20 100644 --- a/tf/modules/lambda/main.tf +++ b/tf/modules/lambda/main.tf @@ -1,32 +1,56 @@ +resource "aws_lambda_function" "lambda" { + function_name = "${var.app}-lambda-function-${var.env}" + description = var.lambda_description + filename = var.lambda_file_location + handler = var.lambda_handler_file + role = aws_iam_role.iam_for_lambda.arn # TODO + runtime = var.lambda_runtime + memory_size = var.lambda_memory_size + timeout = var.lambda_timeout -resource "aws_lambda_function" "era_commons_lambda" { - function_name = join("-", ["lambda-edis-user-api", var.env]) - role = aws_iam_role.iam_for_lambda.arn # TODO - description = "Lambda function contains eRA Commons External Users Info REST APIs implementation." - handler = "src/lambda.handler" - runtime = "nodejs12.x" - memory_size = 2048 - timeout = 30 - tracing_config { - mode = "Active" - } environment { variables = { - "LOG_LEVEL" = "info" + "LOG_LEVEL" = var.lambda_log_level "TABLE" = var.ddb-table-name } } + + tracing_config { + mode = var.lambda_tracing_mode + } + tags = { - app = "userinfoapi" + app = var.app + tier = var.env } - filename = "../lambda-zip/lambda-userapi/lambda-userapi.zip" } -resource "aws_lambda_function_event_invoke_config" "era_commons_lambda" { - function_name = aws_lambda_function.era_commons_lambda.function_name - maximum_retry_attempts = 0 +resource "aws_lambda_function_event_invoke_config" "lambda" { + function_name = aws_lambda_function.lambda.function_name + maximum_retry_attempts = var.lambda_config_retry_attempts } -output "lambda_arn" { - value = aws_lambda_function.era_commons_lambda.arn -} \ No newline at end of file +resource "aws_iam_role" "iam_for_lambda" { + name = "${var.must-be-role-prefix}-lambda-user-api-${var.env}" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Sid = "" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) + managed_policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", + "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess", + "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess" + ] + path = "/" + permissions_boundary = var.must-be-policy-arn +} diff --git a/tf/modules/lambda/outputs.tf b/tf/modules/lambda/outputs.tf new file mode 100644 index 0000000..115311f --- /dev/null +++ b/tf/modules/lambda/outputs.tf @@ -0,0 +1,3 @@ +output "lambda_arn" { + value = aws_lambda_function.lambda.arn +} diff --git a/tf/modules/lambda/roles.tf b/tf/modules/lambda/roles.tf deleted file mode 100644 index 316cbbe..0000000 --- a/tf/modules/lambda/roles.tf +++ /dev/null @@ -1,26 +0,0 @@ - -resource "aws_iam_role" "iam_for_lambda" { - name = "${var.must-be-role-prefix}-lambda-user-api-${var.env}" - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Effect = "Allow" - Sid = "" - Principal = { - Service = "lambda.amazonaws.com" - } - } - ] - }) - managed_policy_arns = [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", - "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess", - "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess" - ] - path = "/" - permissions_boundary = var.must-be-policy-arn -} - diff --git a/tf/modules/lambda/variables.tf b/tf/modules/lambda/variables.tf index 8de2732..959bbae 100644 --- a/tf/modules/lambda/variables.tf +++ b/tf/modules/lambda/variables.tf @@ -1,16 +1,72 @@ +variable "app" { + type = string + description = "Provide the name of the application or the service" +} + +variable "lambda_description" { + type = string + description = "Provide a description for the lambda function" +} + +variable "lambda_handler_file" { + type = string + description = "The source location for the lambda handler definition (i.e. src/handler.lambda)" +} + +variable "lambda_runtime" { + type = string + description = "The runtime for the lambda function. The default value represents the latest runtime version validated for this configuration" + default = "nodejs12.x" +} + +variable "lambda_memory_size" { + type = number + description = "Amount of memory allocated to your Lambda Function, used at runtime (in MB)." + default = 2048 +} + +variable "lambda_timeout" { + type = number + description = "Amount of time your Lambda Function has to run (in seconds)." + default = 30 +} + +variable "lambda_file_location" { + type = string + description = "Path to the function's deployment package within the local filesystem (i.e. '../lambda-zip/lambda-userapi/lambda-userapi.zip')" +} + +variable "lambda_log_level" { + type = string + description = "Set the log level for your Lambda Function" + default = "info" +} + +variable "lambda_tracing_mode" { + type = string + description = "Whether to to sample and trace a subset of incoming requests with AWS X-Ray. Valid values are PassThrough and Active. If PassThrough, Lambda will only trace the request from an upstream service if it contains a tracing header with 'sampled=1'. If Active, Lambda will respect any tracing header it receives from an upstream service. If no tracing header is received, Lambda will call X-Ray for a tracing decision." +} + +variable "lambda_config_retry_attempts" { + type = number + description = "Maximum number of times to retry when the function returns an error. Valid values between 0 and 2." + default = 0 +} + variable "env" { - default = "" + type = string + description = "The target environment for the deployment" } variable "must-be-role-prefix" { - default = "" + type = string description = "Mandatory IAM role name prefix" } variable "must-be-policy-arn" { - default = "" + type = string description = "Mandatory policy to be included in any IAM role" } variable "ddb-table-name" { - default = "" + type = string description = "Dynamo DB table name to connect Lambda function to Dynamo DB" } diff --git a/tf/providers.tf b/tf/providers.tf index c5d0d5d..0b7c641 100644 --- a/tf/providers.tf +++ b/tf/providers.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { - source = "hashicorp/aws" + source = "hashicorp/aws" version = ">= 4.0" } } diff --git a/tf/variables.tf b/tf/variables.tf index aaf3d1d..87350e0 100644 --- a/tf/variables.tf +++ b/tf/variables.tf @@ -1,18 +1,18 @@ variable "role-prefix" { - type = string + type = string description = "Must be prefix to any IAM role" - default = "power-user" + default = "power-user" } variable "policy-boundary-arn" { - type = string + type = string description = "Must be policy to include in any IAM role" - default = "" + default = "" } variable "env" { - type = string - default = "dev" + type = string + default = "dev" description = "Environment tier" }