Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
code share

SSL Certificate Report

Problem this snippet solves:

This script creates a text report detailing all invalid or soon to expire certificates in /config/ssl/ssl.crt/ using openssl to write out the certificate attributes.

Comments on this Snippet
Comment made 18-Jul-2017 by jaikumar_f5 1929

I am getting the below error for v11.x version. The cert path had been changed to certificate_d/

certificatereport.tcl: script failed to complete:
can't eval proc: "script::run"
unable to convert date-time string "Jul , 16:06:24"
    while executing
"clock scan "[lindex $startparts 0] [lindex $startparts 1], [lindex $startparts 3]""
    (procedure "script::run" line 23)
    invoked from within
"script::run" line:1
script did not successfully complete, status:1
0
Comment made 22-Sep-2017 by Jason Adams

I updated the script to:

1 - Enclose all 'exec' command statements in curly braces. 2 - Resolve the formatting of the regsub commands:

FROM:

      regsub -all -- {[['''space''']]+} $start " " start
      regsub -all -- {[['''space''']]+} $stop " " stop

TO:

      regsub -all -- {[[:space:]]+} $start " " start
      regsub -all -- {[[:space:]]+} $stop " " stop

I suspect this occurred during a DevCentral update at some point, so hope this is still helpful.

NOTE: There is a built-in command for this as well:

tmsh run sys crypto check-cert { log enabled stdout enabled verbose enabled }

For help on the command:

tmsh help sys crypto check-cert
0
Comment made 24-Sep-2017 by jaikumar_f5 1929

Works like charm now. Thank you. I had to remove the total-signing-status not-all-signed from the script to make it work. It was throwing with errors.

Syntax Error: "total-signing-status" read-only property

But its weird it got auto added post I saved.

:Active:In Sync] # tmsh list cli script certificatereport.tcl
cli script certificatereport.tcl {
proc script::run {} {
    # Iterate through certs in files
    set hostname [exec {/bin/hostname}]
    set reportdate [exec {/bin/date}]

    puts "---------------------------------------------------------------------"
    puts "Certificate report for BIG-IP $hostname "
    puts "Report Date: $reportdate"
    puts "---------------------------------------------------------------------"
    puts "\n\n"

    set certcount 0
    set certproblems 0
    set certwarnings 0

    foreach file [glob -directory /config/filestore/files_d/Common_d/certificate_d/ *.crt_*] {
        incr certcount
        # Get Certificate Subject
        set cn [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-subject" "|" "grep" "subject"] "=" ] end]
        set start [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-startdate" "|" "grep" "Before"] '='] 1]
        set stop  [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-enddate" "|" "grep" "After"] '='] 1]
        # Clean up bad X509 date fields removing multiple spaces before tokenizing them
        regsub -all -- {[[:space:]]+} $start " " start
        regsub -all -- {[[:space:]]+} $stop " " stop
        set startparts [split $start]
        set stopparts [split $stop]
        set activatedseconds [expr {[clock scan "[lindex $startparts 0] [lindex $startparts 1], [lindex $startparts 3]"] - [clock seconds]}]
        set expiredseconds [expr {[clock seconds] - [clock scan "[lindex $stopparts 0] [lindex $stopparts 1], [lindex $stopparts 3]"]}]
        # Date Math
        if { $activatedseconds > 0 } {
            puts "File: $file"
            puts "\tCN: $cn certificate"
            puts "\tError: certificate is not valid yet.  It will be valid on $start."
            puts "\tActivates in: [expr {$activatedseconds / 86400}] days."
            puts "---------------------------------------------------------------------"
            incr certproblems
        } elseif { $expiredseconds > 0 } {
            puts "File: $file"
            puts "\tCN: $cn certificate"
            puts "\tError: is not valid because it expired on $stop."
            puts "\tExpired: [expr {$expiredseconds / 86400}] days ago."
            puts "---------------------------------------------------------------------"
            incr certproblems
        } elseif { [expr {$expiredseconds * -1}] < 2629743 } {
            # All certs that will expire within this month
            puts "File: $file"
            puts "\tCN: $cn certificate"
            puts "\tError: is not valid because it expired on $stop."
            puts "\tWill Expired in: [expr {$expiredseconds / -86400}] days."
            puts "---------------------------------------------------------------------"
            incr certwarnings
        }
    }
    puts "\n"
    puts "$certcount Certificates Found"
    puts "$certproblems Certificate Errors Found"
    puts "$certwarnings Certificate Warnings Found"
}
    total-signing-status not-all-signed
}
1
Comment made 4 months ago by Mohammed M Irfan 116

Hi Jaikumar,

I am getting an error while creating tcl file.

Error: Syntax Error: "total-signing-status" is a read-only property

Shall i too remove that line from script which you said.

can you please explain why we need to remove it

Thanks

0
Comment made 4 months ago by Mohammed M Irfan 116

Hi Janson,

I have create cli script tcl file and pasted the shared above scrip.

i am new in this, can you please help me in finding the output.

/var/log/ltm ? is this path where i can see the expire SSL certificates or else?

Thanks

Mohammed

0
Comment made 4 months ago by Samir Jha 2941

Can you check the folder where certificatereport.tcl has created(Just guessing) Or grep the cert name in /config file

Ex: grep certa* .

0
Comment made 4 months ago by Mohammed M Irfan 116

No , i am not getting the output in log file.

/var/log

cat ltm | grep certa*

No Logs

0
Comment made 4 months ago by jaikumar_f5 1929

Hi Mohammed,

This is a cli script, when you execute the above shared script, it will not save the output in the /var/log/ path. The output will be in console itself.

:Active:In Sync] ~ # tmsh run cli script certificatereport.tcl
---------------------------------------------------------------------
Certificate report for BIG-IP hostname.company.com
Report Date: Tue Nov  6 15:16:32 GMT 2018
---------------------------------------------------------------------

File: /config/filestore/files_d/Common_d/certificate_d/:Common:application_name.crt
        CN: abc.com certificate
        Error: is not valid because it expired on Oct 3 13:30:09 2018 GMT.
        Expired: 10 days ago.
---------------------------------------------------------------------

If you require the output to be saved in the /var/ path, it requires modification. I will explain step-by-step of this existing sript, It will look something like below,

Please follow the below steps to create the script,

1st step is to create cli script, inside the tmsh, run the create cli script command,

(Active)(/Common)(tmos)# create cli script certificatereport.tcl

Once you hit enter, you'll see something like below - these are default 4 procedures,

create script certificatereport.tcl {

proc script::init {} {
}

proc script::run {} {
}

proc script::help {} {
}

proc script::tabc {} {
}

}

Since we have the script already, just delete all the lines, once you delete, it will be something like below,

create script certificatereport.tcl {
}

Now paste the code, so the final script will look something like below,

create script certificatereport.tcl {
proc script::run {} {
    # Iterate through certs in files
    set hostname [exec {/bin/hostname}]
    set reportdate [exec {/bin/date}]

    puts "---------------------------------------------------------------------"
    puts "Certificate report for BIG-IP $hostname "
    puts "Report Date: $reportdate"
    puts "---------------------------------------------------------------------"
    puts "\n\n"

    set certcount 0
    set certproblems 0
    set certwarnings 0

    foreach file [glob -directory /config/filestore/files_d/Common_d/certificate_d/ *.crt_*] {
        incr certcount
        # Get Certificate Subject
        set cn [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-subject" "|" "grep" "subject"] "=" ] end]
        set start [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-startdate" "|" "grep" "Before"] '='] 1]
        set stop  [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-enddate" "|" "grep" "After"] '='] 1]
        # Clean up bad X509 date fields removing multiple spaces before tokenizing them
        regsub -all -- {[[:space:]]+} $start " " start
        regsub -all -- {[[:space:]]+} $stop " " stop
        set startparts [split $start]
        set stopparts [split $stop]
        set activatedseconds [expr {[clock scan "[lindex $startparts 0] [lindex $startparts 1], [lindex $startparts 3]"] - [clock seconds]}]
        set expiredseconds [expr {[clock seconds] - [clock scan "[lindex $stopparts 0] [lindex $stopparts 1], [lindex $stopparts 3]"]}]
        # Date Math
        if { $activatedseconds > 0 } {
            puts "File: $file"
            puts "\tCN: $cn certificate"
            puts "\tError: certificate is not valid yet.  It will be valid on $start."
            puts "\tActivates in: [expr {$activatedseconds / 86400}] days."
            puts "---------------------------------------------------------------------"
            incr certproblems
        } elseif { $expiredseconds > 0 } {
            puts "File: $file"
            puts "\tCN: $cn certificate"
            puts "\tError: is not valid because it expired on $stop."
            puts "\tExpired: [expr {$expiredseconds / 86400}] days ago."
            puts "---------------------------------------------------------------------"
            incr certproblems
        } elseif { [expr {$expiredseconds * -1}] < 2629743 } {
            # All certs that will expire within this month
            puts "File: $file"
            puts "\tCN: $cn certificate"
            puts "\tError: is not valid because it expired on $stop."
            puts "\tWill Expired in: [expr {$expiredseconds / -86400}] days."
            puts "---------------------------------------------------------------------"
            incr certwarnings
        }
    }
    puts "\n"
    puts "$certcount Certificates Found"
    puts "$certproblems Certificate Errors Found"
    puts "$certwarnings Certificate Warnings Found"
}
}

Press esc, save and quit.

Finally run your script,

:Active:In Sync] ~ # tmsh run cli script certificatereport.tcl
---------------------------------------------------------------------
Certificate report for BIG-IP hostname.company.com
Report Date: Tue Nov  6 15:16:32 GMT 2018
---------------------------------------------------------------------

File: /config/filestore/files_d/Common_d/certificate_d/:Common:application_name.crt
        CN: abc.com certificate
        Error: is not valid because it expired on Oct 3 13:30:09 2018 GMT.
        Expired: 10 days ago.
---------------------------------------------------------------------
1
Comment made 4 months ago by jaikumar_f5 1929

To get your output in a file, you can try this, this is easy way to do rather than modifying the script.

Active:In Sync] ~ # > /var/tmp/cert-output.txt
Active:In Sync] ~ # tmsh run cli script certificatereport.tcl > /var/tmp/cert-output.txt
1
Comment made 4 months ago by Mohammed M Irfan 116

Hi Jaikumar,

Thanks for the detail steps. I followed the step by step. i have run script but i don't found any of "will expire" or "Expired on"

(tmos)# run cli script certificatereport.tcl


Certificate report for BIG-IP BIG-IP_A_v12.com

Report Date: Wed Nov 7 21:17:40 IST 2018


3 Certificates Found

0 Certificate Errors Found

0 Certificate Warnings Found

Can you please help to resolve this!

0
Comment made 4 months ago by jaikumar_f5 1929

Hi Mohammed,

Can you pls run the below cmd & list the files,

#ls -ltrh config/filestore/files_d/Common_d/certificate_d/*

I believe ur on v12, not sure if the cert location in v12 is different.

0
Comment made 4 months ago by Mohammed M Irfan 116

Hi Jaikumar,

Please find the below output.

[root@BIG-IP_A_v12:Active:Standalone] config #
[root@BIG-IP_A_v12:Active:Standalone] config # ls -ltrh config/filestore/files_d/Common_d/certificate_d/*
ls: cannot access config/filestore/files_d/Common_d/certificate_d/*: No such file or directory

Running 12.1.2 Virtual Edition

[root@BIG-IP_A_v12:Active:Standalone] config # tmsh show sys version
Sys::Version
Main Package
Product     BIG-IP
Version     12.1.2
Build       0.0.249
Edition     Final
Date        Wed Nov 30 16:04:00 PST 2016
0
Comment made 4 months ago by Mohammed M Irfan 116

Hi Jaikumar,

I have run the below command to verify the directory! and found three certificates listed below.

[root@BIG-IP_A_v12:Active:Standalone] config # cd filestore/files_d/Common_d/certificate_d/
[root@BIG-IP_A_v12:Active:Standalone] certificate_d # ls *.crt_*
:Common:ca-bundle.crt_19697_1  :Common:default.crt_19695_1  :Common:f5-irule.crt_19693_1

If I change the directory path as show in starting script i.e. -directory /config/ssl/ssl.crt/ *.crt, then also I don't find the output.

[root@BIG-IP_A_v12:Active:Standalone] config # cd /config/ssl/ssl.crt/
[root@BIG-IP_A_v12:Active:Standalone] ssl.crt # ls *.crt_*
ls: cannot access *.crt_*: No such file or directory
[root@BIG-IP_A_v12:Active:Standalone] ssl.crt #
[root@BIG-IP_A_v12:Active:Standalone] ssl.crt #
[root@BIG-IP_A_v12:Active:Standalone] ssl.crt #
[root@BIG-IP_A_v12:Active:Standalone] ssl.crt # ls
ca-bundle.crt  default.crt  dtca-bundle.crt  dtca.crt  dtdi.crt  f5-irule.crt
0
Comment made 4 months ago by jaikumar_f5 1929

Then the script is working totally fine,

(tmos)# run cli script certificatereport.tcl

Certificate report for BIG-IP BIG-IP_A_v12.com

Report Date: Wed Nov  7 21:17:40 IST 2018

3 Certificates Found

Because there's just 3 certs only. Can you run this command as well,

tmsh show sys crypto cert all

0
Comment made 4 months ago by Mohammed M Irfan 116

Hi Jaikumar,

I got the results!, I have create new expire SSL cert for next 6days, 7days, 8days and 10days. and by default their 3 more ssl cert.

Thanks for support and responding!!!

root@(BIG-IP_A_v12)(cfg-sync Standalone)(Active)(/Common)(tmos)# run cli script certificatereport.tcl
---------------------------------------------------------------------
Certificate report for BIG-IP BIG-IP_A_v12.com
Report Date: Fri Nov  9 23:51:57 IST 2018
---------------------------------------------------------------------

File: /config/filestore/files_d/Common_d/certificate_d/:Common:Test_10days.com.crt_39448_1
    CN: Test_10days.com certificate
    Error: is not valid because it expired on Nov 19 09:52:09 2018 GMT.
    Will Expired in: 9 days.
---------------------------------------------------------------------
File: /config/filestore/files_d/Common_d/certificate_d/:Common:Test_8days.com.crt_39442_1
    CN: Test_8days.com certificate
    Error: is not valid because it expired on Nov 17 09:51:48 2018 GMT.
    Will Expired in: 7 days.
---------------------------------------------------------------------
File: /config/filestore/files_d/Common_d/certificate_d/:Common:Test_6days.com.crt_39416_1
    CN: Test_6days.com certificate
    Error: is not valid because it expired on Nov 15 09:50:07 2018 GMT.
    Will Expired in: 5 days.
---------------------------------------------------------------------
File: /config/filestore/files_d/Common_d/certificate_d/:Common:Test_7days.com.crt_39436_1
    CN: Test_7days.com certificate
    Error: is not valid because it expired on Nov 16 09:51:28 2018 GMT.
    Will Expired in: 6 days.
---------------------------------------------------------------------

7 Certificates Found
0 Certificate Errors Found
4 Certificate Warnings Found

One question still remains same, as i was looking for SSL Cert Expire within 7days or less than.

As you can see in output, i am getting all the SSL Cert Expire i.e. 6, 7, 8, 10 Days, respectively.

My expecting result is 6 and 7 days SSL Cert should be appear.

0
Comment made 4 months ago by jaikumar_f5 1929

I have answered in your thread, hope it helps.

1
Comment made 1 month ago by Sumit7595 54

Hi Jaikumar,

How to have this script run automatically every month and get the details through an email?

Regards, Sumit

0
Comment made 1 month ago by Jason Adams

Sumit7595: That's a great question.

The first thing that comes to mind is that you would convert this script to an iCall script and execute that script with with an icall handler.
(tmos)# create /sys icall script certificatereport

##### You can then log everything to /var/log/ltm by changing all of the `puts` commands to `tmsh::log warning`, like this:

sys icall script certificatereport {
    app-service none
    definition {
        # Iterate through certs in files
      set hostname [exec {/bin/hostname}]
      set reportdate [exec {/bin/date}]

      tmsh::log warning "CERTIFICATEREPORT: ---------------------------------------------------------------------"
      tmsh::log warning "CERTIFICATEREPORT: Certificate report for BIG-IP $hostname "
      tmsh::log warning "CERTIFICATEREPORT: Report Date: $reportdate"
      tmsh::log warning "CERTIFICATEREPORT: ---------------------------------------------------------------------"
      tmsh::log warning "CERTIFICATEREPORT: \n\n"

      set certcount 0
      set certproblems 0
      set certwarnings 0

      foreach file [glob -directory /config/filestore/files_d/Common_d/certificate_d/ *.crt_*] {
          incr certcount
          # Get Certificate Subject
          set cn [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-subject" "|" "grep" "subject"] "=" ] end]
          set start [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-startdate" "|" "grep" "Before"] '='] 1]
          set stop  [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-enddate" "|" "grep" "After"] '='] 1]
          # Clean up bad X509 date fields removing multiple spaces before tokenizing them
          regsub -all -- {[[:space:]]+} $start " " start
          regsub -all -- {[[:space:]]+} $stop " " stop
          set startparts [split $start]
          set stopparts [split $stop]
          set activatedseconds [expr {[clock scan "[lindex $startparts 0] [lindex $startparts 1], [lindex $startparts 3]"] - [clock seconds]}]
          set expiredseconds [expr {[clock seconds] - [clock scan "[lindex $stopparts 0] [lindex $stopparts 1], [lindex $stopparts 3]"]}]
          # Date Math
          if { $activatedseconds > 0 } {
              tmsh::log warning "CERTIFICATEREPORT: File: $file"
              tmsh::log warning "CERTIFICATEREPORT: \tCN: $cn certificate"
              tmsh::log warning "CERTIFICATEREPORT: \tError: certificate is not valid yet.  It will be valid on $start."
              tmsh::log warning "CERTIFICATEREPORT: \tActivates in: [expr {$activatedseconds / 86400}] days."
              tmsh::log warning "CERTIFICATEREPORT: ---------------------------------------------------------------------"
              incr certproblems
          } elseif { $expiredseconds > 0 } {
              tmsh::log warning "CERTIFICATEREPORT: File: $file"
              tmsh::log warning "CERTIFICATEREPORT: \tCN: $cn certificate"
              tmsh::log warning "CERTIFICATEREPORT: \tError: is not valid because it expired on $stop."
              tmsh::log warning "CERTIFICATEREPORT: \tExpired: [expr {$expiredseconds / 86400}] days ago."
              tmsh::log warning "CERTIFICATEREPORT: ---------------------------------------------------------------------"
              incr certproblems
          } elseif { [expr {$expiredseconds * -1}] < 2629743 } {
              # All certs that will expire within this month
              tmsh::log warning "CERTIFICATEREPORT: File: $file"
              tmsh::log warning "CERTIFICATEREPORT: \tCN: $cn certificate"
              tmsh::log warning "CERTIFICATEREPORT: \tError: is not valid because it expired on $stop."
              tmsh::log warning "CERTIFICATEREPORT: \tWill Expired in: [expr {$expiredseconds / -86400}] days."
              tmsh::log warning "CERTIFICATEREPORT: ---------------------------------------------------------------------"
              incr certwarnings
          }
      }
      tmsh::log warning "CERTIFICATEREPORT: \n"
      tmsh::log warning "CERTIFICATEREPORT: $certcount Certificates Found"
      tmsh::log warning "CERTIFICATEREPORT: $certproblems Certificate Errors Found"
      tmsh::log warning "CERTIFICATEREPORT: $certwarnings Certificate Warnings Found"
    }
    description none
    events none
}

Create the periodic handler to execute /sys icall script certificatereport once a week (604800 seconds):

(tmos)# create sys icall handler periodic certificatereport-handler { interval 604800 script certificatereport }

Output to /var/log/ltm (local0) would look like this:

Jan 31 12:28:00 ltm-1.local warning scriptd[25509]: 01420004:4: CERTIFICATEREPORT: ---------------------------------------------------------------------
Jan 31 12:28:00 ltm-1.local warning scriptd[25509]: 01420004:4: CERTIFICATEREPORT: Certificate report for BIG-IP ltm-1.local
Jan 31 12:28:00 ltm-1.local warning scriptd[25509]: 01420004:4: CERTIFICATEREPORT: Report Date: Thu Jan 31 12:28:00 PST 2019
Jan 31 12:28:00 ltm-1.local warning scriptd[25509]: 01420004:4: CERTIFICATEREPORT: ---------------------------------------------------------------------
Jan 31 12:28:00 ltm-1.local warning scriptd[25509]: 01420004:4: CERTIFICATEREPORT:
Jan 31 12:28:10 ltm-1.local warning scriptd[25509]: 01420004:4: CERTIFICATEREPORT:
Jan 31 12:28:10 ltm-1.local warning scriptd[25509]: 01420004:4: CERTIFICATEREPORT: 18 Certificates Found
Jan 31 12:28:10 ltm-1.local warning scriptd[25509]: 01420004:4: CERTIFICATEREPORT: 0 Certificate Errors Found
Jan 31 12:28:10 ltm-1.local warning scriptd[25509]: 01420004:4: CERTIFICATEREPORT: 0 Certificate Warnings Found

Email

There is an example on sending emails through an iCall script here: [https://devcentral.f5.com/questions/the-problem-about-icalls-tmsh-script](https://devcentral.f5.com/questions/the-problem-about-icalls-tmsh-script)

set args "echo \"Failover notification\" | mail -s \"$host is now Standby.  $date\" network_operations@company.com“

if { [catch {  exec  /bin/bash -c $args  }] } {
    puts "\nCould not send email, try again."

So with that in mind, you could add all of the logging information to a variable, then send that out as an email instead:

sys icall script certificatereport {
    app-service none
    definition {
        # Iterate through certs in files

      tmsh::log info "Starting Certificate Report"
      set hostname [exec {/bin/hostname}]
      set reportdate [exec {/bin/date}]

      append REPORT "\n---------------------------------------------------------------------"
      append REPORT "\nCertificate report for BIG-IP $hostname "
      append REPORT "\nReport Date: $reportdate"
      append REPORT "\n---------------------------------------------------------------------"
      append REPORT "\n"

      set certcount 0
      set certproblems 0
      set certwarnings 0

      foreach file [glob -directory /config/filestore/files_d/Common_d/certificate_d/ *.crt_*] {
          incr certcount
          # Get Certificate Subject
          set cn [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-subject" "|" "grep" "subject"] "=" ] end]
          set start [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-startdate" "|" "grep" "Before"] '='] 1]
          set stop  [lindex [split [exec "/usr/bin/openssl" "x509" "-in" $file "-enddate" "|" "grep" "After"] '='] 1]
          # Clean up bad X509 date fields removing multiple spaces before tokenizing them
          regsub -all -- {[[:space:]]+} $start " " start
          regsub -all -- {[[:space:]]+} $stop " " stop
          set startparts [split $start]
          set stopparts [split $stop]
          set activatedseconds [expr {[clock scan "[lindex $startparts 0] [lindex $startparts 1], [lindex $startparts 3]"] - [clock seconds]}]
          set expiredseconds [expr {[clock seconds] - [clock scan "[lindex $stopparts 0] [lindex $stopparts 1], [lindex $stopparts 3]"]}]
          # Date Math
          if { $activatedseconds > 0 } {
              append REPORT "\nFile: $file"
              append REPORT "\n\tCN: $cn certificate"
              append REPORT "\n\tError: certificate is not valid yet.  It will be valid on $start."
              append REPORT "\n\tActivates in: [expr {$activatedseconds / 86400}] days."
              append REPORT "\n---------------------------------------------------------------------"
              incr certproblems
          } elseif { $expiredseconds > 0 } {
              append REPORT "\nFile: $file"
              append REPORT "\n\tCN: $cn certificate"
              append REPORT "\n\tError: is not valid because it expired on $stop."
              append REPORT "\n\tExpired: [expr {$expiredseconds / 86400}] days ago."
              append REPORT "\n---------------------------------------------------------------------"
              incr certproblems
          } elseif { [expr {$expiredseconds * -1}] < 2629743 } {
              # All certs that will expire within this month
              append REPORT "\nFile: $file"
              append REPORT "\n\tCN: $cn certificate"
              append REPORT "\n\tError: is not valid because it expired on $stop."
              append REPORT "\n\tWill Expired in: [expr {$expiredseconds / -86400}] days."
              append REPORT "\n---------------------------------------------------------------------"
              incr certwarnings
          }
      }
      append REPORT "\n\n"
      append REPORT "\n$certcount Certificates Found"
      append REPORT "\n$certproblems Certificate Errors Found"
      append REPORT "\n$certwarnings Certificate Warnings Found"

      set date [clock format [clock seconds] -format "%Y%m%d%H%M%S"]
      set settings [tmsh::get_config sys global-settings]
      set host [tmsh::get_field_value [lindex $settings 0] hostname]

      tmsh::log informational "Generating Certificate Report Email"
      set args "echo \"${REPORT}\" | mail -s \"$host Certificate Report. $date\" administrator@example.com"

      if { [catch {  exec  /bin/bash -c $args  }] } {
          tmsh::log warning "Could not send Certificate Report email, try again."
      }
    }
    description none
    events none
}

NOTE: You must have your system send locally generated emails:

K13180: Configuring the BIG-IP system to deliver locally generated email messages (11.x - 13.x)

1