November 10, 2021 Terraform and Serverless Framework Integration in AWS Develop your infrastructure rapidly and with less overhead by leveraging the strong sides of Serverless Framework and Terraform. Introduction Terraform is a declarative resource configuration language with extensive support for the AWS cloud computing platform, and it is one of the go-to solutions for managing infrastructure as code. Data sources allow Terraform to use the information defined outside of it, defined by separate Terraform configuration, or modified by functions or other IaC implementations. Serverless Framework is an application framework that helps you develop and deploy your AWS Lambda functions, along with the AWS infrastructure resources they require. It manages your code as well as the underlying infrastructure. Challenge The main problem when providing the application development team with access to their own infrastructure (so that they have a flexible deployment process) is defining and retaining the security boundaries over their actions. By default, this is not easily achieved in the Serverless Framework, and here is where the Terraform-defined IaC comes in. When you separate the infrastructure resources between the two, you can draw a clear line of responsibility for the Security management. By using both of them, you can achieve a separation between the shared and app-specific infrastructure. Managing the shared infrastructure of core resources, such as Networking, Security, RDS, Persistent Storage, etc., with Terraform, gives you a single source of truth for all application-specific infrastructures. This allows you to completely separate responsibilities for the application and infrastructure deployments. Leveraging the Serverless Framework allows you to iterate your application release without touching your shared infrastructure. Software releases are decoupled from shared infrastructure, enabling you to focus on the application itself without worrying about infrastructure changes. Solution Let’s take a look at how to integrate both of them in a complex deployment pipeline. For example, we’ll use a Pipeline with a couple of Terraform deployments that handle the Infrastructure not as a single deployment but as multiple deployments that have complex dependencies between elements. We define the application deployment dependencies order so that all dependencies get resolved in the correct order. Each deployment exports a set of SSM Parameter Store parameters that will be used as a Data Source for the next deployments. We can even encrypt the parameters with a specific KMS key to ensure that the development team of the Application 1 doesn’t have access to parameters for Application 2 and vice versa. At the start, we have a Git repository that holds all of our deployments code or just references to it like the pre-deployment version. In the next step, we deploy all of our shared infrastructures, such as VPC networking, basic security groups, and underlying resources, shared between all applications and services. We export references to different resources that will be used as Data sources for the Application Terraform and Serverless Framework deployments either as separate parameters in the SSM Parameter Store per application or as a single parameter that is shared between them. Next, we deploy our Cognito-related elements, such as UserPools, IdentityProviders, and all other Cognito-related resources, such as IAM roles, for example. We also export the reference values of the resources that will be used as Data sources for the next deployment. As of this step, the shared infrastructure deployment and Cognito deployment do not have any references to each other. We continue with the Terraform deployment of the Application 1 stateful infrastructures, such as its RDS security groups, DynamoDB tables, SNS, SQS, and its CodeDeployment project used for the Serverless Framework deployment. Here we reference the SSM Parameter Store parameter as a Data Source that has been exported in the shared infrastructure Terraform deployment so that we can place the resources in the predefined subnets. Next, we run the Serverless Framework deployment for Application 1, which references the shared SSM Parameter Store for both shared infrastructures, Cognito and Application 1 Terraform deployments, and use them to deploy our Application code and its app-specific infrastructure, such as Lambda functions, API Gateways, etc. The Application 2 Terraform deployment is the next step, and it uses the same logic as Application 1, referencing the shared infrastructure SSM Parameter Store parameters. Then we run the Application 2 Serverless Framework deployment and reference the shared infrastructure, Cognito, Application 2 Terraform, and the Application 1 Serverless Framework exported SSM Parameter Store parameters. Application 2 will require some data from Application 1 and, therefore, will access the Private API Gateway created from Application 1. We don’t share direct access to the Database between Applications but access through specific private API endpoints. We continue with the deployment of our Front-End application, which again uses a Terraform infrastructure deployment that handles the CloudFront distribution, S3 bucket, WAF configuration, etc. In the end, we have the Front-End Application deployment that references the Cognito, Front-End Terraform SSM Parameter Store parameters, the Application 1 and Application 2 parameters so that it can have their public endpoints. Now let’s take a look at the exact code used for exporting references to SSM Parameter Store parameters and then the backward process of ingesting it back as a Data Source to the next steps. In the shared infrastructure deployment we create the resources and then export them in the SSM Parameter Store as parameters: resource "aws_security_group" "app1_egress_only" { name = "${local.name}-app1-egress-only" description = "${local.name}-app1-egress-only" vpc_id = local.vpc_id egress { description = "${local.name}-application1-egress-only" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = local.org_tags } resource "aws_kms_key" "app1" { deletion_window_in_days = 7 enable_key_rotation = true policy = data.aws_iam_policy_document.app1_kms_key.json tags = local.org_tags } resource "aws_ssm_parameter" "app1_kms_arn" { name = "/shared_infra/application1/kms_arn" description = "The parameter description" type = "SecureString" value = aws_kms_key.app1.arn key_id = aws_kms_key.app1.arn tags = local.org_tags } resource "aws_ssm_parameter" "app1_security_group_id" { name = "/shared_infra/application1/security_group_id" description = "The parameter description" type = "SecureString" value = aws_security_group.app1_egress_only.id key_id = aws_kms_key.app1.arn tags = local.org_tags } Then we can reference the parameters in the Application 1 Terraform deployment: data "aws_ssm_parameter" "kms_arn" { name = "/shared_infra/application1/kms_arn" } data "aws_ssm_parameter" "security_group_id" { name = "/shared_infra/application1/security_group_id" } locals { kms_arn = data.aws_ssm_parameter.kms_arn.value security_group_id = data.aws_ssm_parameter.security_group_id.value } Viable option is to reference them in the serverless.yml in the Application 1 Serverless Framework deployment, export the desired resources that this deployment creates and are necessary for the next ones: provider: name: aws runtime: nodejs12.x stage: ${opt:stage, 'main'} region: ${opt:region, 'eu-west-2'} versionFunctions: false environment: # Static AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 COGNITO_USER_POOL_ARN: ${self:custom.cognito.endUserPoolArn} Custom: # Custom configurations here starting with the SSM Prefixes ssmSharedInfraPrefix: /shared_infra/application1/ ssmCognitoPrefix: /cognito/application1/ ssmOutputPrefix: /${self:service}/${self:provider.stage}/ # Custom configuration for the VPC config that is reused by the functions VpcConfig: securityGroupIds: ${ssm:${self:custom.ssmSharedInfraPrefix}security_group_id~split} subnetIds: ${ssm:${self:custom.ssmSharedInfraPrefix}subnet_ids~split} # Cognito UserPool references to the ssm parameters cognito: endUserPoolArn: ${ssm:${self:custom.ssmCognitoPrefix}END_USER_POOL_ARN} endUserPoolId: ${ssm:${self:custom.ssmCognitoPrefix}END_USER_POOL_ID} CustomerAuthorizer: arn: ${self:provider.environment.COGNITO_USER_POOL_ARN} # Custom config for the CORS config that is reused by the functions CorsConfig: origin: '*' headers: - Content-Type - Authorization - Access-Control-Allow-Headers allowCredentials: false functions: debug-route: handler: functions/function1/function1.handler events: - http: path: /debug method: GET authorizer: ${self:custom.CustomerAuthorizer} cors: ${self:custom.CorsConfig} vpc: ${self:custom.VpcConfig} resources: Resources: gatewayApiId: Type: AWS::SSM::Parameter Properties: Type: String Name: ${self:custom.ssmOutputPrefix}GATEWAY_API_ID Value: Ref: ApiGatewayRestApi gatewayUrl: Type: AWS::SSM::Parameter Properties: Type: String Name: ${self:custom.ssmOutputPrefix}GATEWAY_URL Value: Fn::Join: - '' - - 'https://' - Ref: ApiGatewayRestApi - '.execute-api.${self:provider.region}.amazonaws.com/${self:provider.stage} ' By implementing the same logic in the rest of the deployments, we can easily handle all dependencies and follow a clear separation over the resources. This enables us to achieve separation by leveraging different KMS keys for the encryptions of the SSM Parameter Store parameters that give access to them, based on IAM permissions for using the KMS keys. Conclusion By combining both methods to build your infrastructure, you are leveraging the strong sides of the Serverless Framework to develop more rapidly with radically less overhead by directly managing the app-specific infrastructure. Referencing the shared infrastructure, managed by Terraform, and using the SSM Parameter Store parameters to target resources from one to the other is another benefit. Serverless Framework’s flexibility to develop and deploy your application is just a layer above the IaC that Terraform manages. Both of them are an integral part of your software delivery pipeline. Tags Cloud & DevOps Share Share on Facebook Share on LinkedIn Share on Twitter Share Share on Facebook Share on LinkedIn Share on Twitter Sign up for our monthly newsletter. Sign up for our monthly newsletter.