Let’s Terraform the vTM: Part 4 / 4
Continuing from Part 1, Part 2, and Part 3, in this final instalment we’ll finish our configuration by adding things like SSL offload and L7 routing.
vTM has an embedded language called TrafficScript, that can be used to run business logic over Requests (what vTM sees from the client) and/or Responses (what vTM sees from the pool node). It has full access to everything in Requests and Responses (if vTM is configured to perform SSL Offload), which includes headers, cookies, request strings, body, etc.. Based on the logic you put into your TrafficScript (TS), vTM can do all kinds of things – check out my older post on integrating 3rd party services with your Single Page App web site, for example. For this post, we’ll use it to do L7 routing.
Terraform supports including external code in-line, which is perfect for small chunks of such code, but I like separating concerns. Create a directory files
under our working directory try-vtmtf
, and inside it create a file called vs1_request_rule.tpl
with the following contents:
if(http.getPath() == "/api") { pool.use("${pool_name}"); }
This is a template for TS code with one variable – pool_name
. Running terraform apply
will inject a name of our API pool to produce the actual TrafficScript code. Our VS1 will then run it on all incoming requests, and if HTTP Path contains the string /api
, vTM will send that request to the nodes in the pool with the name of our API pool that we’ve passed to it.
Let’s add this into our main.tf
:
# Our Request Rule needs API pool name, so we handle this through a template data "template_file" "vs1_request_rule" { template = "${file("${path.module}/files/vs1_request_rule.tpl")}" vars { pool_name = "${vtm_pool.api_pool.name}" } } resource "vtm_rule" "vs1_l7_routes" { name = "${local.uniq_id}_VS1-L7-Routes" content = "${data.template_file.vs1_request_rule.rendered}" }
This will create a template data source called vs1_request_rule
of type template_file
with one variable – pool_name
that we set to the name of our API pool. When accessed, this data source will take the contents of the template file set in template
parameter, inject the variable(s), and return the resulting file.
We can see how this is used in the vtm_rule
resource, where content
parameter consumes what this data source produces.
Since template_file
requires yet another provider, we’ll need to run terraform init
once more before proceeding, so that Terraform can download it. Once done, you can run terraform plan
and see the rendered contents of the template in the proposed creation of the vtm_rule
.
Note: In cases where your rules are small (such as above), you may prefer to include them directly into the template using heredoc syntax. It is certainly more compact, and comes to personal preference as to whether you want to mix Terraform templating code (HCL) with other code (TrafficScript) or not.
Here’s an example for how you could do the same as above (separate TrafficScript file and
data "template_file" "vs1_request_rule"
) using this combined approach, where the TS code is included between theEOF
markers. In this case we inject the pool name by referring directly to the appropriate Terraform resource –${vtm_pool.api_pool.name}
.
resource "vtm_rule" "vs1_l7_routes" { name = "${local.uniq_id}_VS1-L7-Routes" # Content is TrafficScript, with an embedded Terraform variable # content = <<EOF if(http.getPath() == "/api") { pool.use("${vtm_pool.api_pool.name}"); } EOF }
To make this rule active we’ll need to add it to our VS as a parameter; but let’s take care of SSL offload prerequisites first.
Our template requires a certificate, so we are going to create a self-signed one. In our try-vtmtf container, run the following:
openssl req -newkey rsa:2048 -nodes \ -keyout domain.key -out domain.csr \ -subj "/C=US/ST=New York/L=Brooklyn/O=Example Brooklyn Company/CN=*.corp.com" openssl x509 -signkey domain.key \ -in domain.csr -req \ -days 365 -out domain.crt
This should have produced three files – domain.crt
, domain.csr
, and domain.key
. Paste their contents (starting / ending with -----BEGIN.. / -----END..
) into the three new variables in our terraform.tfvars
file between the respective EOF
markers:
ssl_cert_pub = <<EOF -----BEGIN CERTIFICATE----- MIIDUjCCAjoCCQDGcM4C0OfWCDANBgkqhkiG9w0BAQsFADBrMQswCQYDVQQGEwJV [ ... skipped ... ] 64+OgfhZ2bb3eiFsm2Azf55AWqbb5pr3tDwrBidg5R176Cl6l00= -----END CERTIFICATE----- EOF ssl_cert_pri = <<EOF -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCq0CEK/ua37JDM [ ... skipped ... ] htQ3q8eLTYwApsXNHdGwQyME -----END PRIVATE KEY----- EOF ssl_cert_req = <<EOF -----BEGIN CERTIFICATE REQUEST----- MIICsDCCAZgCAQAwazELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREw [ ... skipped ... ] K4nc/E5mq4JkmBEdiDg/yxUnUvI= -----END CERTIFICATE REQUEST----- EOF
Hopefully now it’s double-clear why we don’t want to check terraform.tfvars
into code repository! 🙂
Let’s add the corresponding variables into our variables.tf
:
variable "ssl_cert_pri" { description = "Private SSL Cert for the Virtual Server" } variable "ssl_cert_pub" { description = "Public SSL Cert for the Virtual Server" } variable "ssl_cert_req" { description = "Signing Request for the SSL Cert for the Virtual Server" }
And the code for the SSL Server Key resource to the main.tf
:
# SSL Server Certificates for the Virtual Server's SSL Offload. # resource "vtm_ssl_server_key" "ssl_cert" { name = "${local.uniq_id}-server-corp.com" note = "SSL Server Cert for corp.com" private = "${var.ssl_cert_pri}" public = "${var.ssl_cert_pub}" request = "${var.ssl_cert_req}" }
Finally, let’s update our Virtual Server so that it:
- Uses our TrafficScript Rule;
- Has SSL Offload turned on; and
- Listens on the port 443 instead of 80.
Replace the resource "vtm_virtual_server" "vs1" { .. }
that you have in your main.tf
with the following:
# The Virtual Server # resource "vtm_virtual_server" "vs1" { name = "${local.uniq_id}_VS1" enabled = "true" listen_on_any = "${local.should_listen_on_any}" # We need to use compact() to get rid of values "" which will be there # in case we didn't have any TIP Groups listen_on_traffic_ips = ["${compact(local.tigs_list)}"] # Default pool = "Main" pool = "${vtm_pool.main_pool.name}" port = "443" protocol = "http" ssl_decrypt = "true" ssl_server_cert_default = "${vtm_ssl_server_key.ssl_cert.name}" request_rules = ["${vtm_rule.vs1_l7_routes.name}"] }
The notable changes are:
port
:80
->443
- Added
ssl_decrypt
,ssl_server_cert_default
, andrequest_rules
Now, run terraform apply
, and if all went well – you should have your configuration in its final state, all wrapped up and working!
If not – do not despair 🙂 My copy of the template is available in my GitHub repo, where you can download it and compare with yours.
And once you’re done playing – run terraform destroy
, to see the configuration you’ve applied to your vTM disappear as a whole, leaving the rest of the configuration (if any) intact.
Happy terraforming!
May 17th, 2018 at 9:32 am
[…] See you in the final Part 4! […]