on 13-Dec-2022 08:00
Welcome back! Thank you for joining me in part 3 of our journey with F5 security and AWS Global Accelerator! In part 1 we we covered what we would build and why, in part 2 we built out the test bed, and in this installment we will talk about cloud operations which will get us ready for part 4 - testing your deployment!
There are many ways you can deploy BIG-IP into AWS and there are many different as-code-tools you can use to manage your environments. This is not a discussion of Terraform vs Ansible vs. organization-built tools. How you build your platform is up to you, and just know that F5 has answers to and can support all of those approaches. In this article we are going to focus on how to use AWS AutoScale constructs to scale our environment, the F5 Automation Tool Chain to update security policies for your environment, and how to change instance types (because maybe you want fewer larger instances, or maybe you want many small instances).
For those that have not worked with the F5 Automation Tool Chain, it is the supported set of APIs that F5 presents to manage BIG-IP systems. The Tool Chain has 4 components: Declarative Onboarding (DO) which is focused on instantiation of systems, Application Services 3 (AS3) which is how we enable you to manage applications on BIG-IP, Telemetry Streaming (TS) which allows transformation of logs for different systems, and finally, F5 Application Services Templates (FAST) for templated applications. The F5 Automation Tool Chain is the primary interface for the array of available as-code tools to interface with F5 BIG-IP.
Let's take a moment to review what we have built and then we will look how to handle day to day operations in the cloud:
What we have deployed is an end-to-end elastic system that can scale in and out at all layers as necessary that does not require manual intervention to adjust for fluctuations in traffic. This dynamic nature combined with F5 security context makes it much more difficult for attackers to accomplish their goals of resource exhaustion and security bypass.
So what are the most common things that we will have to address in our environment?
If you recall we instantiated our environment from the CLI and only added in a small set of parameters.
aws cloudformation create-stack --region <REGION> --stack-name <MY NAME> \ --template-url https://f5-cft-v2.s3.amazonaws.com/f5-aws-cloudformation-v2/v220.127.116.11/examples/autoscale/payg/autoscale.yaml \ --parameters ParameterKey=sshKey,ParameterValue=<KEY PAIR> \ ParameterKey=restrictedSrcAddressMgmt,ParameterValue=<MY IP> \ ParameterKey=restrictedSrcAddressApp,ParameterValue=<0.0.0.0/0 or MY IP> \ ParameterKey=uniqueString,ParameterValue=<MY UNIQUE STRING> \ ParameterKey=provisionExternalBigipLoadBalancer,ParameterValue=false \ ParameterKey=notificationEmail,ParameterValue=<MY EMAIL> \ --capabilities CAPABILITY_NAMED_IAM
We did not evaluate all of the parameters that are used. If we look at your example stack you can see these in more detail and note that there is a much larger array of parameters that we can change. Due to space limitations on the site I have removed text to show some examples.
[cloudshell-user@ip-10-0-79-206 ~]$ aws cloudformation describe-stacks --stack-name mywaf --output yaml Stacks: - Capabilities: - CAPABILITY_NAMED_IAM - CAPABILITY_AUTO_EXPAND CreationTime: '2022-10-25T23:12:32.625000+00:00' Description: This template creates BIG-IP PAYG Autoscale WAF solution. The template uses nested templates for provisioning network, access, compute resources for hosting BIG-IP Autoscale solution. DisableRollback: false DriftInformation: StackDriftStatus: NOT_CHECKED EnableTerminationProtection: false LastUpdatedTime: '2022-11-23T02:43:23.402000+00:00' NotificationARNs:  Outputs: - OutputKey: bigIpAutoscaleGroupName ....... - ParameterKey: restrictedSrcAddressMgmt ParameterValue: <MY IP> ....... - ParameterKey: restrictedSrcAddressApp ParameterValue: 0.0.0.0/0 ..... ParameterValue: 10.0.0.0/16 - ParameterKey: notificationEmail ParameterValue: <MY EMAIL> ..... - ParameterKey: bigIpRuntimeInitConfig ParameterValue: https://<MY BUCKET>.s3.amazonaws.com/v7as-security-vs-bot.yaml ..... (END)
If we would like to change something in our environment we should use the Stack update process. While it is true that we could, in some scenarios, simply change objects directly that would cause configuration drift and remove our stack as the source of truth. The agility we are looking to gain from using public cloud is rooted in cloud models of templates, automation, and having the platform do the mundane work. Let's look at several examples.
When working in AWS F5 supports an array of instance types. Different AWS instance types have different network characteristics (varying bandwidth, connections) and different CPU counts (varying network performance, SSL performance, security policy performance) and, in addition, F5 offers different licensed capacity (25MB, 200MB, 1GB/3GB/5GB/10GB/HPVE) in the AWS marketplace. While extremely flexible, this can create a scenario where you may decide to change to a different instance class. My general starting point for customers is C5n, M5, C6 or M6. In the near future I will write a blog post on how to choose an instance.
The good news is that a stack update operation of your cloud formation template is makes it easy to accomplish this task. If you recall we deployed a very vanilla example stack but we did not specify an instance type and we simply accepted the default.
Default Instance Type:
- ParameterKey: bigIpInstanceType ParameterValue: m5.xlarge
If we would like to change the instance to to a m5.xlarge or other F5 validated instance class you will need to update the parameter value. To do this you could use the AWS CLI again with a command such as
aws cloudformation update-stack --stack-name <MY NAME> \ --use-previous-template \ --parameters ParameterKey=sshKey,ParameterValue=<KEY PAIR> \ ParameterKey=restrictedSrcAddressMgmt,ParameterValue=<MY IP> \ ParameterKey=restrictedSrcAddressApp,ParameterValue=<0.0.0.0/0 or MY IP> \ ParameterKey=uniqueString,ParameterValue=<MY UNIQUE STRING> \ ParameterKey=provisionExternalBigipLoadBalancer,ParameterValue=false \ ParameterKey=notificationEmail,ParameterValue=<MY EMAIL> \ ParameterKey=bigIpInstanceType,ParamaterValue=m5.2xlarge --capabilities CAPABILITY_NAMED_IAM
Or you could initiate it from the AWS Web Console
Reusing the existing template
Changing the parameters that you would like to change such as instance type
No matter which method you chose you should end up in a state where the stack update is complete
When we deployed the template there were two parameters that we did not discuss. These parameters represent the minimum and maximum number of instances in our BIG-IP AutoScale group. The default values are a minimum of 1 and a maximum of 50. For many users 1 will not be enough.
By following the same processes as above we can scale our group up and down.
In the graph below I am capturing the number of targets we have in the Global Accelerator target group (remember the custom lambda we deployed to update the group) as our AutoScale group adjusts in size.
This can take some time to run as new instances are deployed as time is needed to deploy an instance, configure it, and have it be ready to accept traffic and there are cool down periods between each instance change.
Your application is going to go through changes, security scans may uncover new vulnerabilities and new CVEs will happen. These are facts that we need to address by updating the security policy, but now we have to do so in an elastic and horizontally scaled system. Just like changing the instance type this is a stack update operation to our template.
If we again refer to the template details above you will see the following parameter and you will also note that it is a custom value.
- ParameterKey: bigIpRuntimeInitConfig ParameterValue: https://<MY BUCKET>.s3.amazonaws.com/v7as-security-vs-bot.yaml
You can also see this paramater in the Web ConsoleIt is this file that we use to pass values to the automation tool chain via cloud-init and BIG-IP declared, which in turn passes configuration values to the BIG-IP instances in our autoscale group such as our WAF and telemetry steaming configurations. Within the file that is passed to our systems via the template we can embed configuration commands and https callouts to objects such as a WAF policy.
Custom_WAF_Policy: class: WAF_Policy url: >- https://<MY BUCKET>/example_security_policy_comprehensive.xml enforcementMode: blocking ignoreChanges: false
By updating the stanza in our config file to a new policy and then applying a stack update we will see all of the instances cycled with the new deployments receiving the updated configuration(s).
With the BIG-IP Application Security Manager (ASM) policy example above we are passing the entire JSON statement in the file. What about the other configuration items that are not system defaults such as certificates, BOT polices or DOS polices? Users have two options.
Declarative interfaces make your life easier as you can store the configurations, version them, reuse them... standardize without having to click, click, click, click your way to oblivion. If we look at a snippet from the configuration below you will see that in the bold lines where I am calling another object created in part of the declaration. You can see it in the "use: /Tenant_1/value" lines.
Creating objects that are shared in the tenant
value: class: ADC schemaVersion: 3.0.0 label: Autoscale remark: Autoscale Tenant_1: class: Tenant Shared: class: Application template: shared shared_pool: class: Pool .......... Custom_HTTP_Profile: class: HTTP_Profile xForwardedFor: true Custom_WAF_Policy: class: WAF_Policy url: >- https://<MY BUCKET>/example_security_policy_comprehensive.xml enforcementMode: blocking ignoreChanges: false
Calling the objects that are shared in the tenant:
HTTPS_Service: class: Application template: https serviceMain: class: Service_HTTPS virtualAddresses: - 0.0.0.0 snat: auto profileHTTP: use: /Tenant_1/Shared/Custom_HTTP_Profile policyWAF: use: /Tenant_1/Shared/Custom_WAF_Policy profileDOS: bigip: /Common/dos profileBotDefense: bigip: /Common/bot-defense ipIntelligencePolicy: bigip: /Common/ip-intelligence pool: /Tenant_1/Shared/shared_pool securityLogProfiles: - use: /Tenant_1/Shared/telemetry_asm_security_log_profile serverTLS: bigip: /Common/clientssl redirect80: false
Perhaps you want to use a "golden AMI" for various reasons. It could be that you have a EHF, or perhaps you do not want to store certificates in a manner that the automation tools can call them or perhaps you just want to use default config options. If that is the case then you can see where objects could be configured in /Common above with the "bigip: /Common/object" such as:
HTTPS_Service: class: Application template: https serviceMain: class: Service_HTTPS virtualAddresses: - 0.0.0.0 ....... bigip: /Common/dos profileBotDefense: bigip: /Common/bot-defense ipIntelligencePolicy: bigip: /Common/ip-intelligence ....... serverTLS: bigip: /Common/clientssl redirect80: false
In this scenario you will need to use a custom AMI.
The good news is that you can create custom AMIs for BYOL or PAY-AS-YOU-GO instances. To do so you need to launch an AMI from the marketplace (with the correct license type) and follow the steps to clone it. To call the AMI you would refer to it as a parameter when you first deploy the stack by referencing the custom image ID.
Note as of the time of this writing using an IP Intelligence Policy requires the use of a prefigured AMI. A github issue has been submitted to add the ability to both configure IP Intelligence polices via AS3 and refer to them.
So what about logging and visibility? If you are familiar with F5 Telemetry Streaming you know that it is an extremely flexible tool. Within our configuration statements passed to the system on boot we configured telemetry streaming to send logs to CloudWatch. From that point we can create dashboards as necessary. CloudWatch is not your only option, you could use ELK, Splunk, or other. The correct choice depends on how your organization want to visualize the environment.
These logs are generated from the logstream that is created in AWS CloudWatch from F5 Telemetry Streaming. Once the data is in CloudWatch (or CloudWatch Metrics) you can build graphs using the AWS query language.
You can created the graphs by querying the cloud watch data. For information on how to build these please see the AWS documentation. The following graph and query are shown as examples
fields sig_ids | sort @timestamp desc | limit 20 | display @timestamp,sig_ids | stats count(*) by sig_ids
You can also experiment with the graph type
You can add graphs from other parts of the environment and from other regions to build a consolidated view:
Number of active instances in my target group:
As you can see that by using the F5 templates, F5 Automation Tool Chain, AWS Global Accelerator and AWS CloudWatch we have created an elastic infrastructure that can span multiple AWS regions allowing use to have a consistent security policy applied to all of our security zones. Join me in part 4 to investigate some of the security tools I used to test the signatures and a forwarding looking discussion on how NGINX App Protect or F5 Distributed Cloud could also be used.