diff --git a/docs/assets/images/api/add-certificate.png b/docs/assets/images/api/add-certificate.png new file mode 100644 index 0000000..f5d9edb Binary files /dev/null and b/docs/assets/images/api/add-certificate.png differ diff --git a/docs/assets/images/api/add-new-mapping.png b/docs/assets/images/api/add-new-mapping.png new file mode 100644 index 0000000..3b40728 Binary files /dev/null and b/docs/assets/images/api/add-new-mapping.png differ diff --git a/docs/assets/images/api/api-ca-architecture.png b/docs/assets/images/api/api-ca-architecture.png new file mode 100644 index 0000000..7ded180 Binary files /dev/null and b/docs/assets/images/api/api-ca-architecture.png differ diff --git a/docs/assets/images/api/api-gateway-execution-endpoint.png b/docs/assets/images/api/api-gateway-execution-endpoint.png new file mode 100644 index 0000000..e4631c3 Binary files /dev/null and b/docs/assets/images/api/api-gateway-execution-endpoint.png differ diff --git a/docs/assets/images/api/api-gateway-no-auth.png b/docs/assets/images/api/api-gateway-no-auth.png new file mode 100644 index 0000000..b1d7a96 Binary files /dev/null and b/docs/assets/images/api/api-gateway-no-auth.png differ diff --git a/docs/assets/images/api/api-gw-acm-config.png b/docs/assets/images/api/api-gw-acm-config.png new file mode 100644 index 0000000..3a7c6ac Binary files /dev/null and b/docs/assets/images/api/api-gw-acm-config.png differ diff --git a/docs/assets/images/api/api-gw-truststore-config.png b/docs/assets/images/api/api-gw-truststore-config.png new file mode 100644 index 0000000..ee10a7d Binary files /dev/null and b/docs/assets/images/api/api-gw-truststore-config.png differ diff --git a/docs/assets/images/api/certificate-manager.png b/docs/assets/images/api/certificate-manager.png new file mode 100644 index 0000000..7cc4f5b Binary files /dev/null and b/docs/assets/images/api/certificate-manager.png differ diff --git a/docs/assets/images/api/client-auth-success.png b/docs/assets/images/api/client-auth-success.png new file mode 100644 index 0000000..581800c Binary files /dev/null and b/docs/assets/images/api/client-auth-success.png differ diff --git a/docs/assets/images/api/cloudwatch-logs.png b/docs/assets/images/api/cloudwatch-logs.png new file mode 100644 index 0000000..fe5efb8 Binary files /dev/null and b/docs/assets/images/api/cloudwatch-logs.png differ diff --git a/docs/assets/images/api/custom-domain-name-configured.png b/docs/assets/images/api/custom-domain-name-configured.png new file mode 100644 index 0000000..9414242 Binary files /dev/null and b/docs/assets/images/api/custom-domain-name-configured.png differ diff --git a/docs/assets/images/api/default-api-endpoint-inactive.png b/docs/assets/images/api/default-api-endpoint-inactive.png new file mode 100644 index 0000000..9664901 Binary files /dev/null and b/docs/assets/images/api/default-api-endpoint-inactive.png differ diff --git a/docs/assets/images/api/default-api-endpoint-warning.png b/docs/assets/images/api/default-api-endpoint-warning.png new file mode 100644 index 0000000..ba02b0b Binary files /dev/null and b/docs/assets/images/api/default-api-endpoint-warning.png differ diff --git a/docs/assets/images/api/default-endpoint-failure.png b/docs/assets/images/api/default-endpoint-failure.png new file mode 100644 index 0000000..3528840 Binary files /dev/null and b/docs/assets/images/api/default-endpoint-failure.png differ diff --git a/docs/assets/images/api/deploy-api.png b/docs/assets/images/api/deploy-api.png new file mode 100644 index 0000000..e6b841c Binary files /dev/null and b/docs/assets/images/api/deploy-api.png differ diff --git a/docs/assets/images/api/disable-default-api-endpoint.png b/docs/assets/images/api/disable-default-api-endpoint.png new file mode 100644 index 0000000..e02c1ed Binary files /dev/null and b/docs/assets/images/api/disable-default-api-endpoint.png differ diff --git a/docs/assets/images/api/dns-record.png b/docs/assets/images/api/dns-record.png new file mode 100644 index 0000000..caabe76 Binary files /dev/null and b/docs/assets/images/api/dns-record.png differ diff --git a/docs/assets/images/api/lambda-function.png b/docs/assets/images/api/lambda-function.png new file mode 100644 index 0000000..2621651 Binary files /dev/null and b/docs/assets/images/api/lambda-function.png differ diff --git a/docs/assets/images/api/postman-no-auth.png b/docs/assets/images/api/postman-no-auth.png new file mode 100644 index 0000000..a3b9d2d Binary files /dev/null and b/docs/assets/images/api/postman-no-auth.png differ diff --git a/docs/how-to-guides/api.md b/docs/how-to-guides/api.md new file mode 100644 index 0000000..350c7b0 --- /dev/null +++ b/docs/how-to-guides/api.md @@ -0,0 +1,195 @@ +# API Gateway mTLS with open-source cloud CA + +A step-by-step guide on implementing mTLS for Amazon API Gateway using our [open-source private cloud CA](https://github.com/serverless-ca/terraform-aws-ca), also published as a [blog post](https://medium.com/@paulschwarzenberger/api-gateway-mtls-with-open-source-cloud-ca-3362438445de). + +![Alt text](../assets/images/api/api-ca-architecture.png?raw=true "API Gateway mTLS Architecture") + +## Introduction + +Programmatic communications between systems at different organisations usually use APIs, in most cases requiring client authentication before providing an API response. Client certificate authentication is an effective and scalable way of ensuring an API is only available to authorised systems. + +Amazon API Gateway can be configured to require mutual Transport Layer Security (mTLS) using client certificate authentication. This requires a private Certificate Authority (CA) to issue client certificates to authorise systems to use the API service. We use our [open-source serverless cloud CA](https://serverlessca.com), a cost-effective, secure private CA which is straightforward to deploy as a Terraform module. + +## Deploy API Gateway without authentication + +We’ll start by setting up an API Gateway open to the world. While we’d never actually do this for a confidential API, it’s useful to do so here for demonstration and learning purposes. + +The following resources will be deployed to your AWS account: + +* REST API Gateway +* Lambda function +* CloudWatch log groups +* IAM policies and roles + +```bash +git clone https://github.com/serverless-ca/api-gateway.git +``` + +* update `backend.tf` with your Terraform state S3 bucket details + +```bash +cd api-gateway +terraform init +terraform plan +terraform apply +``` + +In the AWS console, select API Gateway, and view the deployed `cloud-app-api` REST API: + +![Alt text](../assets/images/api/api-gateway-no-auth.png?raw=true "API Gateway REST API details") + +For the purposes of this how-to guide, we’ll use a Lambda function to provide the response to an API Gateway request. + +In the AWS console, choose Lambda, then the `api-response` Lambda function: + +![Alt text](../assets/images/api/lambda-function.png?raw=true "API Gateway REST API details") + +## Test API Gateway without authentication + +Select the API Gateway link from the Lambda console to view trigger details: + +![Alt text](../assets/images/api/api-gateway-execution-endpoint.png?raw=true "API Endpoint details shown in Lambda console") + +Note that the HTTP method configured is POST, and that the API Gateway is set up with a publicly accessible execute API endpoint, and no authorisation. + +Install [Postman](https://www.postman.com/downloads) on your laptop. You’ll be encouraged to open an account with Postman, however you don’t need to for the purposes of this tutorial. + +Copy the API Endpoint execute API from the AWS console Lambda trigger details above, choose the POST method, and test: + +![Alt text](../assets/images/api/postman-no-auth.png?raw=true "API response with no authentication using Postman") + +You should see the message “successful response from API Gateway lambda function”. + +## Implement open-source serverless CA + +If you haven’t already, set up the [open-source serverless CA](https://serverlessca.com) as detailed in the [Getting Started](../getting-started.md) guide. From a security perspective, a production CA should be in a dedicated AWS account, separate from the AWS account used for the REST API Gateway. + +In this case, you’ll need to update the serverless CA Terraform configuration to allow the user or role logged in to the API Gateway AWS account to access the CA bundle in the external S3 bucket within your CA AWS account. For example, if you’re deploying via an IAM user, add in the optional variable below when calling the serverless CA Terraform module, and then deploy using Terraform. + +```bash +s3_aws_principals = ["arn:aws:iam:::user/"] +``` + +See the [Cloud CA repository](https://github.com/serverless-ca/cloud-ca) as an example of how this can be done in practice. + +The above configuration step isn’t required if you installed the API Gateway in the same AWS account as the serverless CA. + +## Configure custom domain name for API Gateway + +We’ll do the next steps manually, for the purposes of understanding and learning. However in a real environment, these should all be implemented using infrastructure-as-code such as Terraform. + +From a domain which you own, choose an appropriate subdomain for the API gateway. Then create a TLS certificate using AWS Certificate Manager with DNS validation. This will be the API Gateway server certificate, which doesn’t need to be issued by the serverless private CA. + +![Alt text](../assets/images/api/certificate-manager.png?raw=true "AWS Certificate Manager") + +* At API Gateway, Custom Domain Names, press Create +* Enter the custom domain name you’ve chosen for your API Gateway +* Slide mutual TLS authentication to on +* Copy the S3 URI of the bundle PEM file in the CA External S3 bucket +* Copy the Version ID of the bundle PEM file in the CA External S3 bucket + +![Alt text](../assets/images/api/api-gw-truststore-config.png?raw=true "API Gateway mTLS configuration") + +* Choose the ACM certificate issued previously + +![Alt text](../assets/images/api/api-gw-acm-config.png?raw=true "Selection of ACM certificate for API Gateway") + +* Press Create domain name + +## Map API custom domain name to API Gateway + +The newly created API custom domain name must now be mapped to the API Gateway created earlier. + +* At your newly created API custom domain name, select API mappings +* Press Configure API mappings, Add new mappings +* Select the already configured API Gateway and environment + +![Alt text](../assets/images/api/default-api-endpoint-warning.png?raw=true "API Gateway warning of potential mTLS bypass") + +* You’ll see a warning that the default execute API endpoint must be disabled to prevent bypass of mutual TLS +* Select the `cloud-app-api` API Gateway, API Settings, Edit +* Change the default endpoint to Inactive + +![Alt text](../assets/images/api/default-api-endpoint-inactive.png?raw=true "API Gateway settings with default execute endpoint disabled") + +* Press Save changes +* Return to the `cloud-app-api` resources screen + +![Alt text](../assets/images/api/deploy-api.png?raw=true "Deploy API") + +* Press Deploy API +* Choose the dev stage and press Deploy +* Return to the Add new mapping screen which should no longer show the warning + +![Alt text](../assets/images/api/add-new-mapping.png?raw=true "Warning no longer shown") + +* Press Save +* View the custom domain name, now configured for mTLS + +![Alt text](../assets/images/api/custom-domain-name-configured.png?raw=true "API Gateway custom domain mapping") + +## Create DNS entry for API custom domain name + +We need to create a public DNS record to the new API custom domain name. + +* within Route53 for your hosted zone, create a DNS record for the custom domain name +* the CNAME value should be the API endpoint as listed in the custom domain name configuration + +![Alt text](../assets/images/api/dns-record.png?raw=true "Route53 entry for API Gateway custom domain") + +## Test default API endpoint disabled + +First, let’s confirm that the default execute API endpoint is disabled. + +* Open Postman +* Repeat the API call made earlier + +![Alt text](../assets/images/api/default-endpoint-failure.png?raw=true "Test using Postman without a certificate results in a 403 response") + +* You should see a `"Forbidden"` message +* If you still get the previous response, check you’ve deployed the API + +## Test mutual TLS + +* Issue a client certificate to your laptop using the utils\client-cert.py script as described in the serverless CA [Getting Started](../getting-started.md) guide +* this will create the following files in your home directory: + +```bash +certs/client-key.pem +certs/client-cert.pem +certs/client-cert.crt +certs/client-cert-key.pem +``` + +* open Postman +* select Settings, Certificates, Client Certificates, Add Certificate +* Enter the custom domain name +* navigate to the `client-cert.crt` and `client-key.pem` files + +![Alt text](../assets/images/api/add-certificate.png?raw=true "Configuring Postman with client certificates") + +* press Add +* close Settings +* Send a POST request to your custom domain name adding the `/api` path + +![Alt text](../assets/images/api/client-auth-success.png?raw=true "Successful response using Postman with client certificate") + +* The success message should be returned indicating a successful response + +## View certificate details in CloudWatch logs + +Details of the connection can be viewed within CloudWatch logs + +* view the api-gateway-access CloudWatch log + +![Alt text](../assets/images/api/cloudwatch-logs.png?raw=true "API Gateway access logs shows certificate details") + +* details of your certificate connection can be viewed + +👏 🎉 🎊 Congratulations, you’ve set up and tested API Gateway mTLS with the open-source serverless CA 🎆 🌟 🎇 + +## Certificate Revocation + +Amazon API Gateway mTLS doesn’t by default support Certificate Revocation List (CRL) checking. + +This can be implemented using an API Gateway Lambda authorizer, checking against the latest CRL issued by the serverless CA. The Lambda authorizer may also perform additional checks to require the client certificate to have particular certificate distinguished name fields such as a specific Organization Unit (OU). diff --git a/docs/reference.md b/docs/reference.md index 0930383..1d51d56 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -35,6 +35,7 @@ | [rsa\_tls\_cert\_lambda](#module\_rsa\_tls\_cert\_lambda) | ./modules/terraform-aws-ca-lambda | n/a | | [scheduler](#module\_scheduler) | ./modules/terraform-aws-ca-scheduler | n/a | | [scheduler-role](#module\_scheduler-role) | ./modules/terraform-aws-ca-iam | n/a | +| [sns-ca-notifications](#module\_sns-ca-notifications) | ./modules/terraform-aws-ca-sns | n/a | | [step-function](#module\_step-function) | ./modules/terraform-aws-ca-step-function | n/a | | [step-function-role](#module\_step-function-role) | ./modules/terraform-aws-ca-iam | n/a | | [tls\_keygen\_iam](#module\_tls\_keygen\_iam) | ./modules/terraform-aws-ca-iam | n/a | @@ -55,6 +56,8 @@ | [bucket\_prefix](#input\_bucket\_prefix) | First part of s3 bucket name to ensure uniqueness, if left blank a random suffix will be used instead | `string` | `""` | no | | [cert\_info\_files](#input\_cert\_info\_files) | List of file names to be uploaded to internal S3 bucket for processing | `list` | `[]` | no | | [csr\_files](#input\_csr\_files) | List of CSR file names to be uploaded to internal S3 bucket for processing | `list` | `[]` | no | +| [custom\_sns\_topic\_display\_name](#input\_custom\_sns\_topic\_display\_name) | Customised SNS topic display name, leave empty to use standard naming convention | `string` | `""` | no | +| [custom\_sns\_topic\_name](#input\_custom\_sns\_topic\_name) | Customised SNS topic name, leave empty to use standard naming convention | `string` | `""` | no | | [env](#input\_env) | Environment name, e.g. dev | `string` | `"dev"` | no | | [filter\_pattern](#input\_filter\_pattern) | Filter pattern for CloudWatch logs subscription filter | `string` | `""` | no | | [hosted\_zone\_domain](#input\_hosted\_zone\_domain) | Hosted zone domain, e.g. dev.ca.example.com | `string` | `""` | no | @@ -79,6 +82,11 @@ | [runtime](#input\_runtime) | Lambda language runtime | `string` | `"python3.12"` | no | | [s3\_aws\_principals](#input\_s3\_aws\_principals) | List of AWS Principals to allow access to external S3 bucket | `list` | `[]` | no | | [schedule\_expression](#input\_schedule\_expression) | Step function schedule in cron format, interval should normally be the same as issuing\_crl\_days | `string` | `"cron(15 8 * * ? *)"` | no | +| [sns\_email\_subscriptions](#input\_sns\_email\_subscriptions) | List of email addresses to subscribe to SNS topic | `list(string)` | `[]` | no | +| [sns\_lambda\_subscriptions](#input\_sns\_lambda\_subscriptions) | A map of lambda names to arns to subscribe to SNS topic | `map(string)` | `{}` | no | +| [sns\_policy](#input\_sns\_policy) | A string containing the SNS policy, if used | `string` | `""` | no | +| [sns\_policy\_template](#input\_sns\_policy\_template) | Name of SNS policy template file, if used | `string` | `"default"` | no | +| [sns\_sqs\_subscriptions](#input\_sns\_sqs\_subscriptions) | A map of SQS names to arns to subscribe to thSNSis topic | `map(string)` | `{}` | no | | [subscription\_filter\_destination](#input\_subscription\_filter\_destination) | CloudWatch log subscription filter destination, last section of ARN | `string` | `""` | no | | [timeout](#input\_timeout) | Amount of time Lambda Function has to run in seconds | `number` | `180` | no | diff --git a/examples/rsa-public-crl/ca.tf b/examples/rsa-public-crl/ca.tf index 9cfe0d6..02a86a6 100644 --- a/examples/rsa-public-crl/ca.tf +++ b/examples/rsa-public-crl/ca.tf @@ -14,6 +14,8 @@ module "certificate_authority" { public_crl = true cert_info_files = ["tls", "revoked", "revoked-root-ca"] + custom_sns_topic_display_name = "My Company CA Notifications Production" + providers = { aws = aws aws.us-east-1 = aws.us-east-1 # certificates for CloudFront must be in this region diff --git a/main.tf b/main.tf index a9d419c..e764361 100644 --- a/main.tf +++ b/main.tf @@ -190,6 +190,7 @@ module "create_rsa_root_ca_lambda" { domain = var.hosted_zone_domain runtime = var.runtime public_crl = var.public_crl + sns_topic_arn = module.sns-ca-notifications.sns_topic_arn } module "create_rsa_issuing_ca_lambda" { @@ -210,6 +211,7 @@ module "create_rsa_issuing_ca_lambda" { domain = var.hosted_zone_domain runtime = var.runtime public_crl = var.public_crl + sns_topic_arn = module.sns-ca-notifications.sns_topic_arn } module "rsa_root_ca_crl_lambda" { @@ -232,6 +234,7 @@ module "rsa_root_ca_crl_lambda" { domain = var.hosted_zone_domain runtime = var.runtime public_crl = var.public_crl + sns_topic_arn = module.sns-ca-notifications.sns_topic_arn } module "rsa_issuing_ca_crl_lambda" { @@ -254,6 +257,7 @@ module "rsa_issuing_ca_crl_lambda" { domain = var.hosted_zone_domain runtime = var.runtime public_crl = var.public_crl + sns_topic_arn = module.sns-ca-notifications.sns_topic_arn } module "rsa_tls_cert_lambda" { @@ -276,6 +280,7 @@ module "rsa_tls_cert_lambda" { public_crl = var.public_crl max_cert_lifetime = var.max_cert_lifetime allowed_invocation_principals = var.aws_principals + sns_topic_arn = module.sns-ca-notifications.sns_topic_arn } module "cloudfront_certificate" { @@ -369,3 +374,17 @@ module "db-reader-role" { policy = "db_reader" assume_role_policy = "db_reader" } + +module "sns-ca-notifications" { + source = "./modules/terraform-aws-ca-sns" + + project = var.project + function = "ca-notifications" + env = var.env + custom_sns_topic_display_name = var.custom_sns_topic_display_name + custom_sns_topic_name = var.custom_sns_topic_name + kms_key_arn = coalesce(var.kms_arn_resource, module.kms_tls_keygen.kms_arn) + email_subscriptions = var.sns_email_subscriptions + lambda_subscriptions = var.sns_lambda_subscriptions + sqs_subscriptions = var.sns_sqs_subscriptions +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 7481e7d..53e7392 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -56,6 +56,7 @@ nav: - Revocation: revocation.md - Security: security.md - How-to guides: + - API Gateway: how-to-guides/api.md - Application load balancer: how-to-guides/alb.md - IAM Roles Anywhere: how-to-guides/iam.md - Terraform reference: reference.md diff --git a/modules/terraform-aws-ca-lambda/main.tf b/modules/terraform-aws-ca-lambda/main.tf index a19fcc6..3df422e 100644 --- a/modules/terraform-aws-ca-lambda/main.tf +++ b/modules/terraform-aws-ca-lambda/main.tf @@ -61,6 +61,7 @@ resource "aws_lambda_function" "lambda" { ROOT_CA_INFO = jsonencode(var.root_ca_info) ROOT_CRL_DAYS = tostring(var.root_crl_days) ROOT_CRL_SECONDS = tostring(var.root_crl_seconds) + SNS_TOPIC_ARN = var.sns_topic_arn } } diff --git a/modules/terraform-aws-ca-lambda/variables.tf b/modules/terraform-aws-ca-lambda/variables.tf index c6d119a..b8a9372 100644 --- a/modules/terraform-aws-ca-lambda/variables.tf +++ b/modules/terraform-aws-ca-lambda/variables.tf @@ -109,6 +109,10 @@ variable "runtime" { description = "Lambda language runtime" } +variable "sns_topic_arn" { + description = "SNS Topic ARN for Lambda function to publish to" +} + variable "subscription_filter_destination" { description = "CloudWatch log subscription filter destination, last section of ARN" default = "" diff --git a/modules/terraform-aws-ca-sns/data.tf b/modules/terraform-aws-ca-sns/data.tf new file mode 100644 index 0000000..2e0d300 --- /dev/null +++ b/modules/terraform-aws-ca-sns/data.tf @@ -0,0 +1,3 @@ +data "aws_caller_identity" "current" {} + +data "aws_region" "current" {} \ No newline at end of file diff --git a/modules/terraform-aws-ca-sns/locals.tf b/modules/terraform-aws-ca-sns/locals.tf new file mode 100644 index 0000000..29e2a34 --- /dev/null +++ b/modules/terraform-aws-ca-sns/locals.tf @@ -0,0 +1,9 @@ +locals { + sns_topic_display_name = coalesce(var.custom_sns_topic_name, title(replace("${var.project}-${var.function}-${var.env}", "-", " "))) + sns_topic_name = coalesce(var.custom_sns_topic_name, "${var.project}-${var.function}-${var.env}") + + tags = merge(var.tags, { + Terraform = "true" + Name = local.sns_topic_name, + }) +} diff --git a/modules/terraform-aws-ca-sns/main.tf b/modules/terraform-aws-ca-sns/main.tf new file mode 100644 index 0000000..b6ccf83 --- /dev/null +++ b/modules/terraform-aws-ca-sns/main.tf @@ -0,0 +1,37 @@ +resource "aws_sns_topic" "sns_topic" { + name = local.sns_topic_name + display_name = local.sns_topic_display_name + policy = coalesce(var.sns_policy, templatefile("${path.module}/templates/${var.sns_policy_template}.json", { region = data.aws_region.current.id, account_id = data.aws_caller_identity.current.account_id, sns_topic_name = local.sns_topic_name })) + + tags = merge( + var.tags, + tomap( + { "Name" = local.sns_topic_name } + ) + ) + kms_master_key_id = var.kms_key_arn +} + +resource "aws_sns_topic_subscription" "email_subscriptions" { + for_each = toset(var.email_subscriptions) + endpoint = each.key + protocol = "email" + topic_arn = aws_sns_topic.sns_topic.arn + raw_message_delivery = false +} + +resource "aws_sns_topic_subscription" "lambda_subscriptions" { + for_each = var.lambda_subscriptions + endpoint = each.value + protocol = "lambda" + topic_arn = aws_sns_topic.sns_topic.arn + raw_message_delivery = false +} + +resource "aws_sns_topic_subscription" "sqs_subscriptions" { + for_each = var.sqs_subscriptions + endpoint = each.value + protocol = "sqs" + topic_arn = aws_sns_topic.sns_topic.arn + raw_message_delivery = true +} diff --git a/modules/terraform-aws-ca-sns/outputs.tf b/modules/terraform-aws-ca-sns/outputs.tf new file mode 100644 index 0000000..db194dd --- /dev/null +++ b/modules/terraform-aws-ca-sns/outputs.tf @@ -0,0 +1,3 @@ +output "sns_topic_arn" { + value = aws_sns_topic.sns_topic.arn +} \ No newline at end of file diff --git a/modules/terraform-aws-ca-sns/templates/default.json b/modules/terraform-aws-ca-sns/templates/default.json new file mode 100644 index 0000000..70716a5 --- /dev/null +++ b/modules/terraform-aws-ca-sns/templates/default.json @@ -0,0 +1,30 @@ +{ + "Version": "2012-10-17", + "Id": "default_policy", + "Statement": [ + { + "Sid": "default_statement", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "sns:GetTopicAttributes", + "sns:SetTopicAttributes", + "sns:AddPermission", + "sns:RemovePermission", + "sns:DeleteTopic", + "sns:Subscribe", + "sns:ListSubscriptionsByTopic", + "sns:Publish", + "sns:Receive" + ], + "Resource": "arn:aws:sns:${region}:${account_id}:${sns_topic_name}", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "${account_id}" + } + } + } + ] +} \ No newline at end of file diff --git a/modules/terraform-aws-ca-sns/templates/eventbridge.json b/modules/terraform-aws-ca-sns/templates/eventbridge.json new file mode 100644 index 0000000..f977744 --- /dev/null +++ b/modules/terraform-aws-ca-sns/templates/eventbridge.json @@ -0,0 +1,39 @@ +{ + "Version": "2012-10-17", + "Id": "allow_account_access_to_topic_policy", + "Statement": [ + { + "Sid": "allow_account_access_to_topic", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "sns:GetTopicAttributes", + "sns:SetTopicAttributes", + "sns:AddPermission", + "sns:RemovePermission", + "sns:DeleteTopic", + "sns:Subscribe", + "sns:ListSubscriptionsByTopic", + "sns:Publish", + "sns:Receive" + ], + "Resource": "arn:aws:sns:${region}:${account_id}:${sns_topic_name}", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "${account_id}" + } + } + }, + { + "Sid": "allow_eventbridge_access_to_topic", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Action": "sns:Publish", + "Resource": "arn:aws:sns:${region}:${account_id}:${sns_topic_name}" + } + ] +} \ No newline at end of file diff --git a/modules/terraform-aws-ca-sns/variables.tf b/modules/terraform-aws-ca-sns/variables.tf new file mode 100644 index 0000000..6994859 --- /dev/null +++ b/modules/terraform-aws-ca-sns/variables.tf @@ -0,0 +1,63 @@ +variable "project" { + description = "abbreviation for the project, forms the first part of the resource name" + default = "" +} + +variable "function" { + description = "forms the second part of the resource name" + default = "" +} + +variable "env" { + description = "suffix for environment, e.g. dev" + default = "" +} + +variable "custom_sns_topic_display_name" { + description = "Customised SNS topic display name, leave empty to use standard naming convention" + default = "" +} + + +variable "custom_sns_topic_name" { + description = "Customised SNS topic name, leave empty to use standard naming convention" + default = "" +} + +variable "sns_policy" { + description = "A string containing the SNS policy, if used" + default = "" +} + +variable "sns_policy_template" { + description = "Name of SNS policy template file, if used" + default = "default" +} + +variable "kms_key_arn" { + description = "A KMS key arn to be used to encrypt the queue contents at rest" + default = null +} + +variable "email_subscriptions" { + type = list(string) + description = "List of email addresses to subscribe to this topic" + default = [] +} + +variable "lambda_subscriptions" { + type = map(string) + description = "A map of lambda names to arns to subscribe to this topic" + default = {} +} + +variable "sqs_subscriptions" { + type = map(string) + description = "A map of SQS names to arns to subscribe to this topic" + default = {} +} + +variable "tags" { + type = map(string) + default = {} +} \ No newline at end of file diff --git a/variables.tf b/variables.tf index c938808..6c5eb6e 100644 --- a/variables.tf +++ b/variables.tf @@ -23,6 +23,16 @@ variable "csr_files" { default = [] } +variable "custom_sns_topic_display_name" { + description = "Customised SNS topic display name, leave empty to use standard naming convention" + default = "" +} + +variable "custom_sns_topic_name" { + description = "Customised SNS topic name, leave empty to use standard naming convention" + default = "" +} + variable "env" { description = "Environment name, e.g. dev" default = "dev" @@ -196,6 +206,34 @@ variable "schedule_expression" { default = "cron(15 8 * * ? *)" # 8.15 a.m. daily } +variable "sns_email_subscriptions" { + type = list(string) + description = "List of email addresses to subscribe to SNS topic" + default = [] +} + +variable "sns_lambda_subscriptions" { + type = map(string) + description = "A map of lambda names to arns to subscribe to SNS topic" + default = {} +} + +variable "sns_policy" { + description = "A string containing the SNS policy, if used" + default = "" +} + +variable "sns_policy_template" { + description = "Name of SNS policy template file, if used" + default = "default" +} + +variable "sns_sqs_subscriptions" { + type = map(string) + description = "A map of SQS names to arns to subscribe to thSNSis topic" + default = {} +} + variable "subscription_filter_destination" { description = "CloudWatch log subscription filter destination, last section of ARN" default = ""