In this example, we'll show you how to better secure application delivery using the Venafi Ansible Role with your Citrix ADC instance. Adding Venafi enables you to manage certificates more securely as part of the TLS Termination process on your load balancer.
The steps described in this document are typically performed by DevOps engineers or system administrators. Generally, you'll need a basic undestanding of Citrix ADC, Venafi Trust Protection Platform or Venafi Cloud, and the required permissions for completing the tastks described in the example.
An application delivery controller (ADC) is used to increase the capacity and reliability of applications. ADC improves the performance of applications by decreasing the load on associated servers while managing and maintaining application and network sessions. But ADC configuration can become a long process. However, you can actually automate the process by using a configuration management tool.
In this example, we use RedHat Ansible with the Venafi Ansible Role to automate the process of requesting, retrieving and installing a certificate as part of SSL termination on an ADC (specifically, Citrix ADC) for load balancing web traffic. We'll also utilize three HTTP servers contained in a cluster as the endpoints that are sending and receiving web traffic and being managed by Citrix ADC.
Later in this example, you'll generate a certificate for the demo-citrix.venafi.example
domain using the Venafi Ansible Role to request and retrieve it from either Venafi Trust Protection Platform or Venafi Cloud services. Then you'll copy the certificate files (certificate, private key, chain Bundle) to the Citrix ADC. Finally, you'll configure Citrix ADC to distribute the traffic between three NGINX servers using the round-robin load balancing method. Here below you can find a diagram of what we are trying to accomplish.
NOTE In our example, we suggest that you use the round-robin balancing method. But keep in mind that there are other methods that might be more suitable for your specific use case.
BEST PRACTICES In general, be careful when using self-signed certificates because of the inherent risks of no identity verification or trust control. The public and private keys are both held by the same entity. Also, self-signed certificates cannot be revoked; they can only be replaced. If an attacker has already gained access to a system, the attacker can spoof the identity of the subject. Of course, CAs can revoke a certificate only when they discover the compromise.
To perform the tasks described in this example, you'll need:
- The Venafi Ansible Role installed on your machine; you can install it using
ansible-galaxy
as described here - Access to either Venafi Trust Protection Platform or Venafi Cloud services (the
credentials.yml
file is used in this example)- If you are working with Trust Protection Platform, obtain the
access_token
andrefresh_token
using the VCert CLI.
- If you are working with Trust Protection Platform, obtain the
- Administration access to a Citrix ADC instance
- Nitro Python SDK (available from https://www.citrix.com/downloads/netscaler-adc or from the Downloads tab of the Citrix ADC GUI)
- Citrix ADC modules for Ansible installed from
ansible-galaxy
(for installation instructions, see this guide) - A set of three (3) NGINX servers running your application
Here are the steps we'll complete as we go through this example:
- Retrieve a certificate using the Venafi Ansible Role
- Copy the retrieved certificate files to Citrix ADC
- Create a certificate-key pair on Citrix ADC
- Create HTTP back-end services on Citrix ADC
- Create a virtual server on Citrix ADC
- Execute the playbook
The first step is to create the variables.yaml
file, in this file are defined the variables used during the execution of the playbook such as:
- The Citrix ADC management IP address.
- The credentials used to manage the Citrix ADC.
- The CN needed to generate the certificate.
- The Virtual IP and port on which all the HTTPS traffic will be handled.
- The http services (the NGINX servers running the application).
- The name for the certificate files which will be copied to the Citrix ADC.
adc_address: "192.168.5.188"
adc_username: "youruser"
adc_password: "yourpassword"
test_site:
name: "demo-citrix"
domain: "venafi.example"
adc_virtual_ip: "192.168.3.167"
adc_virtual_port: "443"
http_service: 192.168.6.201
port1: 8001
port2: 8002
port3: 8003
cert_name: "{{ test_site.name }}.crt"
key_name: "{{ test_site.name }}.key"
chain_name: "{{ test_site.name }}-ca-bundle.crt"
Start by creating a YAML file named citrix_create_playbook.yaml
, inside, define a name for the playbook, the hosts in which the tasks will be executed, the type of connection to use, the Citrix ADC collection and specify the variables file created in the previous step :
- name: Create Critx ADC Application
hosts: localhost
connection: local
collections: citrix.adc
vars_files:
- variables.yaml
In the following block of instructions the Venafi Ansible Role is being specified along with the variables it needs to request and retrieve the certificate from the Venafi services, by adding these instructions the Ansible Role will:
- Request and retrieve a certificate which common and alternate names are
demo-citrix.venafi.example
. - Create a RSA private key of a size of 2048 bits.
- Generate a chain bundle file where the CA certificate will be place at the end of the file.
- Create a
tmp
directory on the current working directory which will store the retrieved certificate files.- 3 files will be retrieved and stored using the names on the variables file (demo-citrix.{crt,key,-ca-bundle.crt}).
- Simulate the copy of the retrieved files to the remote host by generating a duplicate of them adding the
.remote
extension (the certificate files retrieved are going to be copied to the Citrix ADC using the Citrix ADC Ansible modules that's the reason why the optionscertificate_copy_private_key_to_remote
andcertificate_remote_execution
are set tofalse
).
---
roles:
- role: venafi.ansible_role_venafi
certificate_common_name: "{{ test_site.name }}.{{ test_site.domain }}"
certificate_alt_name: "DNS:{{ test_site.name }}.{{ test_site.domain }}"
certificate_privatekey_type: "RSA"
certificate_privatekey_size: "2048"
certificate_chain_option: "last"
certificate_cert_dir: "./tmp"
certificate_cert_path: "./tmp/{{ cert_name }}"
certificate_chain_path: "./tmp/{{ chain_name }}"
certificate_privatekey_path: "./tmp/{{ key_name }}"
certificate_copy_private_key_to_remote: false
certificate_remote_execution: false
certificate_remote_privatekey_path: "./tmp/{{ key_name }}.remote"
certificate_remote_cert_path: "./tmp/{{ cert_name }}.remote"
certificate_remote_chain_path: "./tmp/{{ chain_name }}.remote"
By adding the following instructions to the playbook, we specify the actions the playbook will execute. Ansible will connect to the Citrix ADC (using the credentials specified in the variables file) and then it will create the key, CA bundle and certificate using the local files retrieved in the previous step.
---
tasks:
- name: Copy Private Key to Citrix ADC {{ adc_address }}
citrix_adc_system_file:
nsip: "{{ adc_address }}"
nitro_user: "{{ adc_username }}"
nitro_pass: "{{ adc_password }}"
nitro_protocol: http
validate_certs: false
state: present
filename: "{{ key_name }}"
filelocation: "/nsconfig/ssl/"
filecontent: "{{ lookup('file', './tmp/' + key_name) }}"
delegate_to: localhost
- name: Copy Certificate to Citrix ADC {{ adc_address }}
citrix_adc_system_file:
nsip: "{{ adc_address }}"
nitro_user: "{{ adc_username }}"
nitro_pass: "{{ adc_password }}"
nitro_protocol: http
validate_certs: false
state: present
filename: "{{ cert_name }}"
filelocation: "/nsconfig/ssl/"
filecontent: "{{ lookup('file', './tmp/' + cert_name + '.remote') }}"
delegate_to: localhost
- name: Copy CA Bundle to Citrix ADC {{ adc_address }}
citrix_adc_system_file:
nsip: "{{ adc_address }}"
nitro_user: "{{ adc_username }}"
nitro_pass: "{{ adc_password }}"
nitro_protocol: http
validate_certs: false
state: present
filename: "{{ chain_name }}"
filelocation: "/nsconfig/ssl"
filecontent: "{{ lookup('file', './tmp/' + chain_name + '.remote') }}"
delegate_to: localhost
To be able to handle the HTTPS requests, Ansible needs to create a cert-key pair on the Citrix ADC using the files you copied in the previous step.
---
- name: Create Certkey on Citrix ADC {{ adc_address }}
citrix_adc_ssl_certkey:
nsip: "{{ adc_address }}"
nitro_user: "{{ adc_username }}"
nitro_pass: "{{ adc_password }}"
nitro_protocol: http
validate_certs: false
state: present
certkey: "{{ test_site.name }}.{{ test_site.domain }}_certkey"
cert: "/nsconfig/ssl/{{ cert_name }}"
key: "/nsconfig/ssl/{{ key_name }}"
After you create the cert-key pair on the Citrix ADC, Ansible needs to create the HTTP services on the Citric ADC instance. These services are the ones that will actually serve the requests (NGINX servers hosting the application). Ansible will use the host and port variables defined in the variables file for each service.
---
- name: Create service-http-1 on Citrix ADC {{ adc_address }}
citrix_adc_service:
nsip: "{{ adc_address }}"
nitro_user: "{{ adc_username }}"
nitro_pass: "{{ adc_password }}"
nitro_protocol: http
validate_certs: false
state: present
name: service-http-1
servicetype: HTTP
ip: "{{ http_service }}"
ipaddress: "{{ http_service }}"
port: "{{ port1 }}"
delegate_to: localhost
- name: Create service-http-2 on Citrix ADC {{ adc_address }}
citrix_adc_service:
nsip: "{{ adc_address }}"
nitro_user: "{{ adc_username }}"
nitro_pass: "{{ adc_password }}"
nitro_protocol: http
validate_certs: false
state: present
name: service-http-2
servicetype: HTTP
ip: "{{ http_service }}"
ipaddress: "{{ http_service }}"
port: "{{ port2 }}"
delegate_to: localhost
- name: Create service-http-3 on Citrix ADC {{ adc_address }}
citrix_adc_service:
nsip: "{{ adc_address }}"
nitro_user: "{{ adc_username }}"
nitro_pass: "{{ adc_password }}"
nitro_protocol: http
validate_certs: false
state: present
name: service-http-3
servicetype: HTTP
ip: "{{ http_service }}"
ipaddress: "{{ http_service }}"
port: "{{ port3 }}"
delegate_to: localhost
Now that HTTP services have been created, Ansible must create a virtual IP address in order to send the external requests to the HTTP back-end services. The following task creates the virtual server and assigns it the virtual IP defined in the variables file, the port, the certificate-key pair and the HTTP services previously created, as well as the round-robin load balancing method which will allow the virtual server to distribute the load between the NGINX servers hosting the application.
---
- name: Create lb vserver on Citrix ADC {{ adc_address }}
citrix_adc_lb_vserver:
nsip: "{{ adc_address }}"
nitro_user: "{{ adc_username }}"
nitro_pass: "{{ adc_password }}"
nitro_protocol: http
validate_certs: false
state: present
name: "vs-{{ test_site.name }}.{{ test_site.domain }}"
servicetype: SSL
timeout: 2
ipv46: "{{ adc_virtual_ip }}"
port: "{{ adc_virtual_port }}"
lbmethod: ROUNDROBIN
ssl_certkey: "{{ test_site.name }}.{{ test_site.domain }}_certkey"
servicebindings:
- servicename: service-http-1
weight: 80
- servicename: service-http-2
weight: 60
- servicename: service-http-3
weight: 40
disabled: no
delegate_to: localhost
After you finish the playbook, use the following command to run it:
ansible-playbook citrix_create_playbook.yaml --ask-vault-pass
If done correctly, you should see output similar to the following:
In this example, we include a playbook that lets you revert the changes made by running citrix_create_playbook.yaml. Use the following command to run it:
ansible-playbook citrix_delete_playbook.yaml