Using Terraform and AS3 to create new services on F5 LTM

I’ve used Postman and then Python to configure new services(virtual servers) on F5 LTM I have in my lab, but now I want to take a new step and go into Infrastructure as a code and use Terraform to push my config. I’ll be using Terraform and an AS3 template to do this. Terraform is an infrastructure as code tool and with AS3, you can deploy an application service configuration on the BIG-IP system using a declarative representational state transfer (REST) API. I am not going to explain more about these 2, I just want to share a working example.

The goal here would be to create an application with 2 services.

To do this is will need these files:

  • the AS3 template file
  • the variables files, where I create the variables
  • the tfvars file, where I define my variables
  • the main file, where I put it all together so the template gets rendered
  • certificate file, where I generate a self-signed certificate/key
  • locals file

AS3 template file

The AS3 template is a JSON file where I define how my application which consists of 2 services should look. I’ve started with defining some shared objects that I could use with both services:

            "Shared": {
                "class": "Application",
                "template": "shared",
                "enable": true,
                "${POOL1}": {
                    "class": "Pool",
                    "loadBalancingMode":"${LB_MODE}",
                    "monitors": [
                        "${MONITOR}"
                    ],
                    "members": [
                        {
                            "servicePort": ${SERVICEPORT},
                            "serverAddresses": ${MEMBERS_1}                                               
                        }
                    ]
                },
                "${HTTP_PROFILE}":{
                    "class": "HTTP_Profile",
                    "xForwardedFor": true
                },
                "${IRULE_NAME}": {
                    "class": "iRule",
                    "remark": "choose private pool based URI",
                    "iRule": {
                        "url": "${IRULE}"
                    }
                }                               
            },

Then I went to define my services. I’ve tried to use different ways to define different objects to experiment and have an example of multiple ways of doing things. For example, for irule I’ve used “url” to fetch it from Git. Then I used “use” for the objects from the Shared ‘application’ and also used some built-in options like for the TCP profile.
Here is an example of one of the services:

                "${SERVICE2}": {
                    "class": "Service_HTTPS",
                    "virtualAddresses": [
                        "${VIP2}"
                        ],
                    "virtualPort": 443,
                    "persistenceMethods": [
                        "${PERSISTANCE}"
                        ],
                    "profileTCP": { 
                        "egress": "wan",
                        "ingress": "lan"
                        },
                    "profileHTTP": {
                        "use": "/${TENANT}/Shared/${HTTP_PROFILE}"
                        },
                    "snat": "auto",
                    "iRules": [
                        { "use": "/${TENANT}/Shared/${IRULE_NAME}"}
                    ],
                    "pool": "${S2_POOL1}",
                    "serverTLS": "webtls"
                    },
                    "${S2_POOL1}": {
                        "class": "Pool",
                        "monitors": [
                            "${S2_MONITOR}"
                        ],
                        "members": [
                            {
                                "servicePort": ${SERVICEPORT},
                                "serverAddresses": ${S2_MEMBERS_1}                                               
                            }
                        ]
                    },                                       
                    "webtls": {
                        "class": "TLS_Server",
                        "cipherGroup": {"bigip": "/Common/f5-secure"},
                        "tls1_0Enabled": false,
                        "tls1_1Enabled": false,
                        "ssl3Enabled": false,
                        "sslEnabled": false,
                        "certificates": [{
                          "certificate": "webcert"
                        }]
                    },
                      "webcert": {
                        "class": "Certificate",
                        "remark": "in practice we recommend using a passphrase",
                        "certificate": { 
                            "base64": "${cert}"
                        },
                        "privateKey": {
                            "base64": "${key}"
                        }                  
                      }
                }
        }
    }
}

variables files

In these files, I’ve created the variables. I gave 2 files, one for each service.

variable "SERVICE2" {
  type = string
}
variable "VIP2" {
  type = string
}
variable "S2_POOL1" {
  type = string
}
variable "S2_MONITOR" {
  type = string
}
variable "S2_MEMBERS_1" {
  type = list
}

tfvars file

In this file, I am defining what values, the variables I’ve created previously, should have like pool names, irule url, profiles, pool members, etc. for each of my services.

// Tenant 1 config variables
//
// Service 1
TENANT = "Tenant_01"
SERVICE1 = "service_01"
VIP = "10.0.0.131"
HTTP_PROFILE = "http-xForwardedFor"
PERSISTANCE = "cookie"
IRULE_NAME = "rule1"
IRULE = "https://raw.githubusercontent.com/czirakim/F5_AS3/master/Tenant1/rule1.irule"
POOL1 = "webpool"
POOL2 = "abc_pool"
LB_MODE = "least-connections-member"
MONITOR = "http"
MEMBERS_1 = ["1.1.1.1","10.9.9.9"]
MEMBERS_2 = ["8.8.8.8","8.8.4.4"]
SERVICEPORT = 8080
//
// Service2
SERVICE2= "service_02"
VIP2 = "10.0.0.132"
S2_POOL1 = "service_02_webpool"
S2_MONITOR = "http"
S2_MEMBERS_1 = ["1.1.1.2","9.9.9.9"]

main file

In the main file, I put all the things together so the template gets rendered and all the variables get mapped to the ones used in the template.

data template_file "init" {
  template = file("tenant_template.json")
  vars = {
    TENANT = var.TENANT
    SERVICE1 = var.SERVICE1
    SERVICE2 = var.SERVICE2
    VIP = var.VIP
    VIP2 = var.VIP2
    HTTP_PROFILE= var.HTTP_PROFILE
    IRULE_NAME= var.IRULE_NAME
    IRULE= var.IRULE
    POOL1 = var.POOL1
    POOL2 = var.POOL2
    LB_MODE = var.LB_MODE
    PERSISTANCE = var.PERSISTANCE
    MONITOR = var.MONITOR
    MEMBERS_1 = jsonencode(var.MEMBERS_1)
    MEMBERS_2 = jsonencode(var.MEMBERS_2)
    SERVICEPORT = var.SERVICEPORT
    S2_POOL1 = var.S2_POOL1
    S2_MONITOR = var.S2_MONITOR
    S2_MEMBERS_1 = jsonencode(var.S2_MEMBERS_1)
    }
  }
resource "bigip_as3" "as3-tenant" {
     as3_json = data.template_file.init.rendered
     }

certificate file

Here I generate a self-signed certificate in Terraform. This should only be used internally. In the case of production services, these should be replaced with something else. There are some examples on the Internet of how to get a certificate using AWS, GCP, etc.

resource "tls_private_key" "example" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

resource "tls_self_signed_cert" "example" {
  private_key_pem = tls_private_key.example.private_key_pem  
  subject {
    common_name  = "example.com"
    organization = "ACME Examples, Inc"
    country = "US"
  }

  validity_period_hours =  8766 //1 year

  dns_names = ["example.com", "example.net"]  

  allowed_uses = [
    "key_encipherment",
    "digital_signature",
    "server_auth",
  ]
}

locals file

In this file, I am defining some local variables for the cert and the key I’ll be using in my template. They take the values of the resources I’ve created in the certificate file.

locals {
  cert = base64encode(tls_self_signed_cert.example.cert_pem)
  key = base64encode(tls_private_key.example.private_key_pem)
}

Conclusion

So this is it, I have to thank the F5 DevCentral community for helping and check the useful links if you want to learn more about AS3.

Source code: https://github.com/czirakim/F5_AS3/tree/master

Useful links:
https://my.f5.com/manage/s/article/K23449665
https://community.f5.com/t5/technical-articles/embracing-as3-foundations/ta-p/324982
https://clouddocs.f5.com/products/extensions/f5-appsvcs-extension/latest/declarations/http-services.html#configuring-an-http-profile-with-a-proxy-connect-profile
https://clouddocs.f5.com/products/extensions/f5-appsvcs-extension/latest/refguide/declaration-purpose-function.html

About the author

Mihai is a Senior Network Engineer with more than 15 years of experience