Skip to content

Commit

Permalink
Manage CSP reporter resources
Browse files Browse the repository at this point in the history
Imported from infra-csp-reporter in govuk-aws
  • Loading branch information
samsimpson1 committed Jan 27, 2025
1 parent 6cec068 commit d20bc36
Show file tree
Hide file tree
Showing 10 changed files with 855 additions and 1 deletion.
90 changes: 90 additions & 0 deletions terraform/deployments/csp-reporter/api_gateway.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
locals {
# TODO: replace this with tfe_output when the publishing domain is managed by TF
publishing_domain = var.govuk_environment == "production" ? "publishing.service.gov.uk" : "${var.govuk_environment}.publishing.service.gov.uk"
}

# TODO: replace this data source with an output when certs are managed in this repo
data "aws_acm_certificate" "csp_reporter" {
domain = data.tfe_outputs.vpc.nonsensitive_values.external_root_zone_name
statuses = ["ISSUED"]
}

resource "aws_apigatewayv2_api" "csp_reporter" {
name = "CSP reporter"
protocol_type = "HTTP"
description = "Receive CSP reports"
}

resource "aws_apigatewayv2_domain_name" "csp_reporter" {
domain_name = "csp-reporter.${local.publishing_domain}"

domain_name_configuration {
certificate_arn = data.aws_acm_certificate.csp_reporter.arn
endpoint_type = "REGIONAL"
security_policy = "TLS_1_2"
}
}

resource "aws_apigatewayv2_api_mapping" "csp_reporter" {
api_id = aws_apigatewayv2_api.csp_reporter.id
domain_name = aws_apigatewayv2_domain_name.csp_reporter.id
stage = aws_apigatewayv2_stage.default.id
}

resource "aws_apigatewayv2_integration" "csp_reporter" {
api_id = aws_apigatewayv2_api.csp_reporter.id
integration_type = "AWS_PROXY"

description = "Receive Content Security Policy reports and post them to Firehose for storage"
integration_method = "POST"
integration_uri = aws_lambda_function.lambda.invoke_arn
payload_format_version = "2.0"
}

resource "aws_apigatewayv2_route" "report_route" {
api_id = aws_apigatewayv2_api.csp_reporter.id
route_key = "POST /report"

target = "integrations/${aws_apigatewayv2_integration.csp_reporter.id}"
}

resource "aws_apigatewayv2_stage" "default" {
api_id = aws_apigatewayv2_api.csp_reporter.id
name = "$default"
auto_deploy = true

access_log_settings {
destination_arn = aws_cloudwatch_log_group.csp_reporter_log_group.arn
# Using Apache Common Log Format (see: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html)
format = "$context.identity.sourceIp - - [$context.requestTime] \"$context.httpMethod $context.routeKey $context.protocol\" $context.status $context.responseLength $context.requestId"
}
}

resource "aws_cloudwatch_log_group" "csp_reporter_log_group" {
name = "/aws/apigateway/csp-reporter"
retention_in_days = 30
}

resource "aws_lambda_permission" "gateway_invoke_csp_reports_to_firehose_function" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lambda.function_name
principal = "apigateway.amazonaws.com"
source_arn = "arn:aws:execute-api:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${aws_apigatewayv2_api.csp_reporter.id}/*/*/report"
}

resource "aws_route53_record" "csp_reporter" {
name = "csp-reporter.${data.tfe_outputs.vpc.nonsensitive_values.external_root_zone_name}"
type = "A"
zone_id = data.tfe_outputs.vpc.nonsensitive_values.external_root_zone_id

alias {
name = aws_apigatewayv2_domain_name.csp_reporter.domain_name_configuration[0].target_domain_name
zone_id = aws_apigatewayv2_domain_name.csp_reporter.domain_name_configuration[0].hosted_zone_id
evaluate_target_health = false
}
}

output "api_gateway_api_endpoint" {
value = aws_apigatewayv2_api.csp_reporter.api_endpoint
description = "API endpoint of the API"
}
21 changes: 21 additions & 0 deletions terraform/deployments/csp-reporter/buckets.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Bucket to store data from Kinesis Firehose, stores both successes and errors
resource "aws_s3_bucket" "csp_reports" {
bucket = "govuk-${var.govuk_environment}-csp-reports"

tags = {
Name = "govuk-${var.govuk_environment}-csp-reports"
}
}

resource "aws_s3_bucket_lifecycle_configuration" "csp_reports_lifecycle" {
bucket = aws_s3_bucket.csp_reports.id

rule {
id = "govuk-${var.govuk_environment}-csp-reports-lifecycle"
status = "Enabled"

expiration {
days = 30
}
}
}
126 changes: 126 additions & 0 deletions terraform/deployments/csp-reporter/firehose.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
resource "aws_kinesis_firehose_delivery_stream" "delivery_stream" {
name = "govuk-${var.govuk_environment}-csp-reports-stream"
destination = "extended_s3"

extended_s3_configuration {
role_arn = aws_iam_role.firehose_role.arn
bucket_arn = aws_s3_bucket.csp_reports.arn

prefix = "reports/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/"
error_output_prefix = "errors/!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/"

buffering_size = 64
buffering_interval = 600

data_format_conversion_configuration {
input_format_configuration {
deserializer {
open_x_json_ser_de {}
}
}

output_format_configuration {
serializer {
orc_ser_de {}
}
}

schema_configuration {
database_name = aws_glue_catalog_table.reports.database_name
role_arn = aws_iam_role.firehose_role.arn
table_name = aws_glue_catalog_table.reports.name
}
}
}

depends_on = [aws_iam_role_policy.firehose_glue_policy]
}

data "aws_iam_policy_document" "firehose_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["firehose.amazonaws.com"]
}
}
}

resource "aws_iam_role" "firehose_role" {
name = "govuk-${var.govuk_environment}-csp-reports-firehose-role"
assume_role_policy = data.aws_iam_policy_document.firehose_role.json
}

# The firehose role policies are distinct so that we can apply this one
# before the kinesis delivery stream is set-up, as we need to use this access
# to create the delivery stream. The other ones aren't needed before the
# delivery stream's creation
data "aws_iam_policy_document" "firehose_glue_policy" {
statement {
effect = "Allow"
actions = [
"glue:GetTable",
"glue:GetTableVersion",
"glue:GetTableVersions"
]
resources = [
"arn:aws:glue:${var.aws_region}:${data.aws_caller_identity.current.account_id}:catalog",
aws_glue_catalog_database.csp_reports.arn,
aws_glue_catalog_table.reports.arn
]
}
}
resource "aws_iam_role_policy" "firehose_glue_policy" {
name = "govuk-${var.govuk_environment}-csp-reports-firehose-glue-policy"
role = aws_iam_role.firehose_role.id

policy = data.aws_iam_policy_document.firehose_glue_policy.json
}

data "aws_iam_policy_document" "firehose_bucket_policy" {
statement {
effect = "Allow"
actions = ["s3:*"]
resources = [
aws_s3_bucket.csp_reports.arn,
"${aws_s3_bucket.csp_reports.arn}/*"
]
}
statement {
effect = "Allow"
actions = [
"kinesis:DescribeStream",
"kinesis:GetShardIterator",
"kinesis:GetRecords",
"kinesis:ListShards"
]
resources = [aws_kinesis_firehose_delivery_stream.delivery_stream.arn]
}
}

resource "aws_iam_role_policy" "firehose_bucket_policy" {
name = "govuk-${var.govuk_environment}-csp-reports-firehose-bucket-policy"
role = aws_iam_role.firehose_role.id

policy = data.aws_iam_policy_document.firehose_bucket_policy.json
}

data "aws_iam_policy_document" "firehose_kinesis_policy" {
statement {
actions = [
"kinesis:DescribeStream",
"kinesis:GetShardIterator",
"kinesis:GetRecords",
"kinesis:ListShards"
]
resources = [aws_kinesis_firehose_delivery_stream.delivery_stream.arn]
}
}

resource "aws_iam_role_policy" "firehose_kinesis_policy" {
name = "govuk-${var.govuk_environment}-csp-reports-firehose-kinesis-policy"
role = aws_iam_role.firehose_role.id

policy = data.aws_iam_policy_document.firehose_kinesis_policy.json
}
Loading

0 comments on commit d20bc36

Please sign in to comment.