Using CryptoNice as a Sanity-Checking Tool for Automated Application Deployments with Ansible

Any good automation pipeline should have some validation built in to perform sanity checks on what it should have done. This article describes how you can use CryptoNice as a simple and easy way of sanity checking the SSL/TLS configuration of your automated application deployments, by integrating CryptoNice into your pipeline directly after your automation solution has deployed the application.

I am going to show a simple Ansible playbook that illustrates this point. The Ansible playbook deploys an application to a BIG-IP that resides in AWS, but it could be any BIG-IP on premises or in any cloud. After the deployment is complete, I use CryptoNice to validate that the application is reachable via TLS and that the deployed VIP is using best practices for SSL deployments.

What Is CryptoNice?

CryptoNice is both a command line tool and Python library that is developed by F5 Labs and is publicly available; it provides the ability to scan and report on the configuration of SSL/TLS for your internet or internal-facing web services. Built using the sslyze API and SSL, http-client, and DNS libraries, CryptoNice collects data on a given domain and performs a series of tests to check TLS configuration.

You can get CryptoNice here: https://github.com/F5-Labs/cryptonice

What Is Ansible?

Ansible is an open-source software-provisioning, configuration-management, and application-deployment tool enabling infrastructure as code. It runs on many Unix-like systems, and can configure both Unix-like systems as well as Microsoft Windows and also F5 BIG-IPs.

You can learn how to install Ansible here: https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html

F5 publishes instructions on how to integrate the Ansible plugins here: https://clouddocs.f5.com/products/orchestration/ansible/devel/

 

In this article I use the published version 1 F5 Ansible plugin that uses the iControl REST API. If we take a look briefly under the hood here, this plugin is using an imperative API. As of today, F5 has a version 2 plugin in preview that focuses on managing F5 BIG-IP/BIG-IQ through declarative APIs such as AS3, DO, TS, and CFE. You can also check the version 2 plugin preview out on F5 Cloud Docs.

I used a stock Ubuntu image as a basis for my Ansible server and followed the instructions for installing Ansible on Ubuntu, then followed the instructions for installing the F5 plugin for Ansible referenced above.

You can take a look at the F5/Ansible 1.0 plugin that is published on the Ansible Galaxy Hub here: https://galaxy.ansible.com/f5networks/f5_modules

 

For my demonstration, I also installed CryptoNice on the same server where Ansible and the F5 Ansible plugin are installed; at that point you are off to the races and you can build a simple Ansible script and begin to automate a BIG-IP.

The F5 Ansible Module provides you with a great deal of programmability for the BIG-IP basic onboarding, WAF, APM, and LTM. The following is a great reference to give you an idea of the scope of capabilities with Ansible examples that this module provides. You can also automate the infrastructure creation if you so choose, meaning you could stand up a BIG-IP in AWS and then configure everything required to get the device onboarded, and then after that, configure traffic-management objects like VIPs/pools, etc.

https://clouddocs.f5.com/products/orchestration/ansible/devel/modules/module_index.html

For my test, I created a simple Ansible script that automates the creation of a VIP with an iRule to respond to http get requests. The example also associates a pool and pool members to the VIP to give you an idea of what a simple application deployment may look like. After that, I run a CryptoNice to test the quality of the SSL/TLS.

My simple Ansible playbook looks like this:

---


- name: Create a VIP, pool and pool members
  hosts: f5
  connection: local


  vars:
    provider:
      password: notmypassword
      server: f5cove.me
      user: auser
      validate_certs: no
      server_port: 8443


  tasks:
    - name: Create a pool
      bigip_pool:
        provider: "{{ provider }}"
        lb_method: ratio-member
        name: examplepool
        slow_ramp_time: 120
      delegate_to: localhost


    - name: Add members to pool
      bigip_pool_member:
        provider: "{{ provider }}"
        description: "webserver {{ item.name }}"
        host: "{{ item.host }}"
        name: "{{ item.name }}"
        pool: examplepool
        port: '80'
      with_items:
        - host: 10.0.0.68
          name: web01
        - host: 10.0.0.67
          name: web02
      delegate_to: localhost


    - name: Create a VIP
      bigip_virtual_server:
        provider: "{{ provider }}"
        description: avip
        destination: 10.0.0.66
        name: vip-1
        irules:
          - responder
        pool: examplepool
        port: '443'
        snat: Automap
        profiles:
          - http
          - f5cove
      delegate_to: localhost


    - name: Create Ridirect
      bigip_virtual_server:
        provider: "{{ provider }}"
        description: avipredirect
        destination: 10.0.0.66
        name: vip-1-redirect
        irules:
          - _sys_https_redirect
        port: '80'
        snat: None
        profiles:
          - http
      delegate_to: localhost 


- name: play cryptonice nicely
  hosts: 127.0.0.1
  connection: local
  tasks:
  - pause:
      seconds: 30


  - name: run cryptonice
    shell: "cryptonice f5cove.me"
    register: output


  - debug: var=output.stdout_lines

 

My output from running the playbook looks like this:

PLAY [Create a VIP, pool and pool members] *************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************************************************************
ok: [1.2.3.4]

TASK [Create a pool] ***********************************************************************************************************************************************************************************************************************
ok: [1.2.3.4 -> localhost]

TASK [Add members to pool] *****************************************************************************************************************************************************************************************************************
ok: [1.2.3.4 -> localhost] => (item={'host': '10.0.0.68', 'name': 'web01'})
ok: [1.2.3.4 -> localhost] => (item={'host': '10.0.0.67', 'name': 'web02'})

TASK [Create a VIP] ************************************************************************************************************************************************************************************************************************
changed: [1.2.3.4 -> localhost]

TASK [Create Ridirect] *********************************************************************************************************************************************************************************************************************
ok: [1.2.3.4 -> localhost]

PLAY [play cryptonice nicely] **************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [pause] *******************************************************************************************************************************************************************************************************************************
Pausing for 30 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
ok: [localhost]

TASK [run cryptonice] **********************************************************************************************************************************************************************************************************************
changed: [localhost]


TASK [debug] *******************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "output.stdout_lines": [
        "Pre-scan checks",
        "-------------------------------------",
        "Scanning f5cove.me on port 443...",
        "Analyzing DNS data for f5cove.me",
        "Fetching additional records for f5cove.me",
        "f5cove.me resolves to 34.217.132.104",
        "34.217.132.104:443: OPEN",
        "TLS is available: True",
        "Connecting to port 443 using HTTPS",
        "Queueing TLS scans (this might take a little while...)",
        "Looking for HTTP/2",
        "",
        "",
        "RESULTS",
        "-------------------------------------",
        "Hostname:\t\t\t  f5cove.me",
        "",
        "Selected Cipher Suite:\t\t  ECDHE-RSA-AES128-GCM-SHA256",
        "Selected TLS Version:\t\t  TLS_1_2",
        "",
        "Supported protocols:",
        "TLS 1.2:\t\t\t  Yes",
        "TLS 1.1:\t\t\t  Yes",
        "TLS 1.0:\t\t\t  Yes",
        "",
        "TLS fingerprint:\t\t  29d29d15d29d29d21c29d29d29d29d930c599f185259cdd20fafb488f63f34",
        "",
        "",
        "",
        "CERTIFICATE",
        "Common Name:\t\t\t  f5cove.me",
        "Issuer Name:\t\t\t  R3",
        "Public Key Algorithm:\t\t  RSA",
        "Public Key Size:\t\t  2048",
        "Signature Algorithm:\t\t  sha256",
        "",
        "Certificate is trusted:\t\t  False (Mozilla not trusted)",
        "Hostname Validation:\t\t  OK - Certificate matches server hostname",
        "Extended Validation:\t\t  False",
        "Certificate is in date:\t\t  True",
        "Days until expiry:\t\t  29",
        "Valid From:\t\t\t  2021-02-12 23:28:14",
        "Valid Until:\t\t\t  2021-05-13 23:28:14",
        "",
        "OCSP Response:\t\t\t  Successful",
        "Must Staple Extension:\t\t  False",
        "",
        "Subject Alternative Names:",
        "\t  f5cove.me",
        "",
        "Vulnerability Tests:",
        "No vulnerability tests were run",
        "",
        "HTTP to HTTPS redirect:\t\t  False",
        "None",
        "",
        "RECOMMENDATIONS",
        "-------------------------------------",
        "HIGH - TLSv1.0 Major browsers are disabling TLS 1.0 imminently. Carefully monitor if clients still use this protocol. ",
        "HIGH - TLSv1.1 Major browsers are disabling this TLS 1.1 immenently. Carefully monitor if clients still use this protocol. ",
        "Low - CAA Consider creating DNS CAA records to prevent accidental or malicious certificate issuance.",
        "",
        "Scans complete",
        "-------------------------------------",
        "Total run time: 0:00:05.688562"
    ]
}


Note that after the playbook is complete, CryptoNice is telling me that I need to improve the quality of my SSL profile on my BIG-IP.

As a result, I create a second SSL profile and

  • Disable TLS 1.0 and 1.1.
  • Upgrade the certificate from a 2048 bit key to a 4096 bit key.
  • Make sure that the certificate bundle is configured correctly to ensure that the certificate is properly trusted.
  • Create a cipher rule and group and use the following cipher string to improve the quality of the SSL connections:

 

!EXPORT:!DHE+AES-GCM:!DHE+AES:ECDHE+AES-GCM:ECDHE+AES:RSA+AES-GCM:RSA+AES:-MD5:-SSLv3:-RC4:!3DES

 

I then alter the playbook to reference the new SSL profile, and then re-run the Ansible playbook.

Here is the relevant playbook snippet:

   - name: Create a VIP
      bigip_virtual_server:
        provider: "{{ provider }}"
        description: avip
        destination: 10.0.0.66
        name: vip-1
        irules:
          - responder
        pool: examplepool
        port: '443'
        snat: Automap
        profiles:
          - http
          - f5cove <change this to the new SSL Profile>
      delegate_to: localhost

 

 

The resulting output from CryptoNice after running the playbook again to switch the SSL profile addresses all of the HIGH recommendations.

TASK [debug] **********************************************************************************************************************************************************************
ok: [localhost] => {
    "output.stdout_lines": [
        "Pre-scan checks",
        "-------------------------------------",
        "Scanning f5cove.me on port 443...",
        "Analyzing DNS data for f5cove.me",
        "Fetching additional records for f5cove.me",
        "f5cove.me resolves to 34.217.132.104",
        "34.217.132.104:443: OPEN",
        "TLS is available: True",
        "Connecting to port 443 using HTTPS",
        "Queueing TLS scans (this might take a little while...)",
        "Looking for HTTP/2",
        "",
        "",
        "RESULTS",
        "-------------------------------------",
        "Hostname:\t\t\t  f5cove.me",
        "",
        "Selected Cipher Suite:\t\t  ECDHE-RSA-AES256-GCM-SHA384",
        "Selected TLS Version:\t\t  TLS_1_2",
        "",
        "Supported protocols:",
        "TLS 1.2:\t\t\t  Yes",
        "",
        "TLS fingerprint:\t\t  2ad2ad0002ad2ad0002ad2ad2ad2adcb09dd549309271837f87ac5dad15fa7",
        "",
        "",
        "HTTP/2 supported:\t\t  False",
        "",
        "",
        "CERTIFICATE",
        "Common Name:\t\t\t  f5cove.me",
        "Issuer Name:\t\t\t  R3",
        "Public Key Algorithm:\t\t  RSA",
        "Public Key Size:\t\t  4096",
        "Signature Algorithm:\t\t  sha256",
        "",
        "Certificate is trusted:\t\t  True (No errors)",
        "Hostname Validation:\t\t  OK - Certificate matches server hostname",
        "Extended Validation:\t\t  False",
        "Certificate is in date:\t\t  True",
        "Days until expiry:\t\t  88",
        "Valid From:\t\t\t  2021-04-13 00:55:09",
        "Valid Until:\t\t\t  2021-07-12 00:55:09",
        "",
        "OCSP Response:\t\t\t  Successful",
        "Must Staple Extension:\t\t  False",
        "",
        "Subject Alternative Names:",
        "\t  f5cove.me",
        "",
        "Vulnerability Tests:",
        "No vulnerability tests were run",
        "",
        "HTTP to HTTPS redirect:\t\t  False",
        "None",
        "",
        "RECOMMENDATIONS",
        "-------------------------------------",
        "Low - CAA Consider creating DNS CAA records to prevent accidental or malicious certificate issuance.",
        "",
        "Scans complete",
        "-------------------------------------",
        "Total run time: 0:00:05.638186"
    ]
}

 

Now you can see in the CryptoNice output that

  • There are no trust issues with the certificate/certificate bundle.
  • I have upgraded from 2048 bits to 4096 bit key length.
  • I have disabled TLS1.0 and TLS1.1.
  • The connection is handshaking with a more secure cryptographic algorithm.
  • I no longer have any HIGH recommendations for improvement to the quality of TLS connection.

Conclusion

It is always best practice to perform a sanity check on the services that you create in your automation pipelines. This article describes using CryptoNice as part of a simple Ansible playbook to run an SSL/TLS sanity check on the application in order to improve the security of your application-delivery automation. Ultimately you should not just be checking the SSL/TLS; another good idea would be to introduce application-vulnerability scanners too as part of the automation pipeline.

Published Apr 27, 2021
Version 1.0
  • Love seeing real world uses of Cryptonice, thanks for sharing Paul! I'm working on some big updates to the tool at the moment... stay tuned! 🙂