This is an automated email from the ASF dual-hosted git repository.

ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new aea565c  Acme auto renew (#5514)
aea565c is described below

commit aea565cb4cddf6b4e0e402adb99c9915b4a97469
Author: mattjackson220 <[email protected]>
AuthorDate: Tue Feb 16 14:41:54 2021 -0700

    Acme auto renew (#5514)
    
    * Added ACME providers to autorenewal script
    
    * updated Changelog and added note to docs
    
    * updates per comments
    
    * updates per comments
---
 CHANGELOG.md                                       |  1 +
 docs/source/admin/traffic_ops.rst                  | 15 ++++++
 docs/source/admin/traffic_router.rst               | 20 ++++++-
 ...liveryservices_sslkeys_generate_letsencrypt.rst |  2 +-
 docs/source/api/v1/letsencrypt_autorenew.rst       | 54 ++++---------------
 ...liveryservices_sslkeys_generate_letsencrypt.rst |  2 +-
 docs/source/api/v2/letsencrypt_autorenew.rst       | 54 ++++---------------
 ...liveryservices_sslkeys_generate_letsencrypt.rst |  2 +-
 docs/source/api/v3/letsencrypt_autorenew.rst       | 54 ++++---------------
 docs/source/api/v4/acme_autorenew.rst              | 49 +++++++++++++++++
 ...liveryservices_sslkeys_generate_letsencrypt.rst |  2 +-
 docs/source/api/v4/letsencrypt_autorenew.rst       | 54 ++++---------------
 infrastructure/cdn-in-a-box/traffic_ops/config.sh  |  4 ++
 traffic_ops/app/conf/cdn.conf                      |  4 ++
 .../templates/send_mail/autorenewcerts_mail.html   | 24 +++++++++
 traffic_ops/traffic_ops_golang/config/config.go    |  7 +++
 .../traffic_ops_golang/deliveryservice/acme.go     | 10 ++--
 .../deliveryservice/autorenewcerts.go              | 61 ++++++++++++++++++----
 .../deliveryservice/letsencryptcert.go             |  2 +-
 traffic_ops/traffic_ops_golang/routing/routes.go   |  3 +-
 20 files changed, 227 insertions(+), 197 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3a730a..8bc4391 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - Traffic Ops Client: New Login function with more options, including falling 
back to previous minor versions. See traffic_ops/v3-client documentation for 
details.
 - Added license files to the RPMs
 - Added ACME certificate renewals and ACME account registration using external 
account binding
+- Added functionality to automatically renew ACME certificates.
 
 ### Fixed
 - [#5288](https://github.com/apache/trafficcontrol/issues/5288) - Fixed the 
ability to create and update a server with MTU value >= 1280.
diff --git a/docs/source/admin/traffic_ops.rst 
b/docs/source/admin/traffic_ops.rst
index 4b620eb..3a2c732 100644
--- a/docs/source/admin/traffic_ops.rst
+++ b/docs/source/admin/traffic_ops.rst
@@ -316,6 +316,13 @@ This file deals with the configuration parameters of 
running Traffic Ops itself.
        :kid:           The key ID provided by the :abbr:`ACME (Automatic 
Certificate Management Environment)` provider for 
ref:`external_account_binding`.
        :hmac_encoded:  The :abbr:`HMAC (Hashed Message Authentication Code)` 
key provided by the :abbr:`ACME (Automatic Certificate Management Environment)` 
provider for ref:`external_account_binding`. This should be in Base64 URL 
encoded.
 
+:acme_renewal: This object contains the information for the automatic renewal 
script for certificates.
+
+       .. versionadded:: 5.1
+
+       :renew_days_before_expiration: Set the number of days before expiration 
date to renew certificates.
+       :summary_email: The email address to use for summarizing certificate 
expiration and renewal status. If it is blank, no email will be sent.
+
 :geniso: This object contains configuration options for system ISO generation.
 
        :iso_root_path: Sets the filesystem path to the root of the ISO 
generation directory. For default installations, this should usually be set to 
:file:`/opt/traffic_ops/app/public`.
@@ -350,8 +357,16 @@ This file deals with the configuration parameters of 
running Traffic Ops itself.
 
        :user_email: A required email address to create an account with Let's 
Encrypt or to receive expiration updates. If this is not included then `rate 
limits <https://letsencrypt.org/docs/rate-limits>`_ may apply for the number of 
certificates.
        :send_expiration_email: A boolean option to send email summarizing 
certificate expiration status
+
+               .. deprecated:: 5.1
+                       Future versions of Traffic Ops will not support this 
legacy configuration option, see acme_renewal: { summary_email: <string> } 
instead.
+
        :convert_self_signed: A boolean option to convert self signed to Let's 
Encrypt certificates as they expire. This only works for certificates labeled 
as Self Signed in the Certificate Source field.
        :renew_days_before_expiration: Set the number of days before expiration 
date to renew certificates.
+
+               .. deprecated:: 5.1
+                       Future versions of Traffic Ops will not support this 
legacy configuration option, see acme_renewal: { renew_days_before_expiration: 
<int> } instead.
+
        :environment: This specifies which Let's Encrypt environment to use: 
'staging' or 'production'. It defaults to 'production'.
 
 :portal: This section provides information regarding a connected UI with which 
users interact, so that emails can include links to it.
diff --git a/docs/source/admin/traffic_router.rst 
b/docs/source/admin/traffic_router.rst
index 6ccce57..56a8ae6 100644
--- a/docs/source/admin/traffic_router.rst
+++ b/docs/source/admin/traffic_router.rst
@@ -833,9 +833,27 @@ Let's Encrypt can be set up through :ref:`cdn.conf` by 
updating the following fi
        
+------------------------------+---------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
        | renew_days_before_expiration | int     | No       | Number of days 
before expiration date to renew certificates                                    
                                                                        |
        
+------------------------------+---------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-       | environment                  | string  | No       | Let's Encrypt 
environment to use.  Options are 'staging' or 'production'. Defaults to 
'production'                                                                    
 |
+       | environment                  | string  | No       | Let's Encrypt 
environment to use.  Options are 'staging' or 'production'. Defaults to 
'production'.                                                                   
 |
        
+------------------------------+---------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
 
+Automatic Certificate Renewal
+-----------------------------
+If desired, an automated certificate renewal script is located at 
:file:`/traffic_ops/etc/cron.d/autorenew_certs`. This job is setup to be run, 
but the file must be updated with the username and password for Traffic Ops in 
order to be run.  In :ref:`cdn.conf` the following fields can be defined in 
order to alter the number of days in advance to renew and send a summary email 
after renewal.
+
+.. note:: In order for this to work, the AuthType field for the certificate 
must match the ACME provider in the :ref:`cdn.conf`.
+
+.. important:: After the automatic renewal script has run, a queue and 
snapshot must be run manually in order for the certificates to be used.
+
+.. table:: Fields to update to run the automatic renewal script under 
`acme_renewal`:
+
+       
+------------------------------+---------+----------+----------------------------------------------------------------------------------------------------------------------------+
+       | Name                         | Type    | Required | Description       
                                                                                
                         |
+       
+==============================+=========+==========+============================================================================================================================+
+       | summary_email                | boolean | No       | The email address 
to use for summarizing certificate expiration and renewal status. If it is 
blank, no email will be sent. |
+       
+------------------------------+---------+----------+----------------------------------------------------------------------------------------------------------------------------+
+       | renew_days_before_expiration | int     | No       | Number of days 
before expiration date to renew certificates. Default is 30 days.               
                            |
+       
+------------------------------+---------+----------+----------------------------------------------------------------------------------------------------------------------------+
+
 .. table:: Fields to update for sending emails under `smtp`
 
        
+------------+------------------+----------------------------------------------------------------------+
diff --git 
a/docs/source/api/v1/deliveryservices_sslkeys_generate_letsencrypt.rst 
b/docs/source/api/v1/deliveryservices_sslkeys_generate_letsencrypt.rst
index bde5f78..c73c40c 100644
--- a/docs/source/api/v1/deliveryservices_sslkeys_generate_letsencrypt.rst
+++ b/docs/source/api/v1/deliveryservices_sslkeys_generate_letsencrypt.rst
@@ -62,7 +62,7 @@ Response Structure
 
        { "alerts": [{
                "level": "success",
-               "text": "Beginning async call to Let's Encrypt for ds-01.  This 
may take a few minutes."
+               "text": "Beginning async call to Let's Encrypt for ds-01. This 
may take a few minutes."
        }]}
 
 .. [#needOne] Either the ``key`` or the ``deliveryservice`` field must be 
provided. If both are provided, then they must match.
diff --git a/docs/source/api/v1/letsencrypt_autorenew.rst 
b/docs/source/api/v1/letsencrypt_autorenew.rst
index 854a931..c8a29e1 100644
--- a/docs/source/api/v1/letsencrypt_autorenew.rst
+++ b/docs/source/api/v1/letsencrypt_autorenew.rst
@@ -36,29 +36,6 @@ No parameters available
 
 Response Structure
 ------------------
-:LetsEncryptExpirations: A list of objects with information regarding 
certificate expiration for all Let's Encrypt certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
-
-:SelfSignedExpirations:  A list of objects with information regarding 
certificate expiration for all self signed certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
-
-:OtherExpirations:       A list of objects with information regarding 
certificate expiration for all other certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
 
 .. code-block:: http
        :caption: Response Example
@@ -66,24 +43,13 @@ Response Structure
        HTTP/1.1 200 OK
        Content-Type: application/json
 
-       { "response": {
-               "LetsEncryptExpirations": [
-                       {
-                               "XmlId":"demo2",
-                               "Version":1,
-                               "Expiration":"2020-08-18T13:53:06Z",
-                               "AuthType":"Lets Encrypt",
-                               "Error":null
-                       }
-               ],
-               "SelfSignedExpirations": [
-                       {
-                               "XmlId":"demo1",
-                               "Version":3,
-                               "Expiration":"2020-08-18T13:53:06Z",
-                               "AuthType":"Self Signed",
-                               "Error":null
-                       }
-               ],
-               "OtherExpirations":null
-       }}
+       { "alerts": [
+               {
+                       "text": "This endpoint is deprecated, please use 
letsencrypt/autorenew instead",
+                       "level": "warning"
+               },
+               {
+                       "text": "Beginning async call to renew certificates. 
This may take a few minutes.",
+                       "level": "success"
+               }
+       ]}
diff --git 
a/docs/source/api/v2/deliveryservices_sslkeys_generate_letsencrypt.rst 
b/docs/source/api/v2/deliveryservices_sslkeys_generate_letsencrypt.rst
index 929ed2e..42dc4b9 100644
--- a/docs/source/api/v2/deliveryservices_sslkeys_generate_letsencrypt.rst
+++ b/docs/source/api/v2/deliveryservices_sslkeys_generate_letsencrypt.rst
@@ -60,7 +60,7 @@ Response Structure
 
        { "alerts": [{
                "level": "success",
-               "text": "Beginning async call to Let's Encrypt for ds-01.  This 
may take a few minutes."
+               "text": "Beginning async call to Let's Encrypt for ds-01. This 
may take a few minutes."
        }]}
 
 .. [#needOne] Either the ``key`` or the ``deliveryservice`` field must be 
provided. If both are provided, then they must match.
diff --git a/docs/source/api/v2/letsencrypt_autorenew.rst 
b/docs/source/api/v2/letsencrypt_autorenew.rst
index 014b013..4cdbce4 100644
--- a/docs/source/api/v2/letsencrypt_autorenew.rst
+++ b/docs/source/api/v2/letsencrypt_autorenew.rst
@@ -34,29 +34,6 @@ No parameters available
 
 Response Structure
 ------------------
-:LetsEncryptExpirations: A list of objects with information regarding 
certificate expiration for all Let's Encrypt certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
-
-:SelfSignedExpirations:  A list of objects with information regarding 
certificate expiration for all self signed certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
-
-:OtherExpirations:       A list of objects with information regarding 
certificate expiration for all other certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
 
 .. code-block:: http
        :caption: Response Example
@@ -64,24 +41,13 @@ Response Structure
        HTTP/1.1 200 OK
        Content-Type: application/json
 
-       { "response": {
-               "LetsEncryptExpirations": [
-                       {
-                               "XmlId":"demo2",
-                               "Version":1,
-                               "Expiration":"2020-08-18T13:53:06Z",
-                               "AuthType":"Lets Encrypt",
-                               "Error":null
-                       }
-               ],
-               "SelfSignedExpirations": [
-                       {
-                               "XmlId":"demo1",
-                               "Version":3,
-                               "Expiration":"2020-08-18T13:53:06Z",
-                               "AuthType":"Self Signed",
-                               "Error":null
-                       }
-               ],
-               "OtherExpirations":null
-       }}
+       { "alerts": [
+               {
+                       "text": "This endpoint is deprecated, please use 
letsencrypt/autorenew instead",
+                       "level": "warning"
+               },
+               {
+                       "text": "Beginning async call to renew certificates. 
This may take a few minutes.",
+                       "level": "success"
+               }
+       ]}
diff --git 
a/docs/source/api/v3/deliveryservices_sslkeys_generate_letsencrypt.rst 
b/docs/source/api/v3/deliveryservices_sslkeys_generate_letsencrypt.rst
index 2d6230a..9f35f8d 100644
--- a/docs/source/api/v3/deliveryservices_sslkeys_generate_letsencrypt.rst
+++ b/docs/source/api/v3/deliveryservices_sslkeys_generate_letsencrypt.rst
@@ -60,7 +60,7 @@ Response Structure
 
        { "alerts": [{
                "level": "success",
-               "text": "Beginning async call to Let's Encrypt for ds-01.  This 
may take a few minutes."
+               "text": "Beginning async call to Let's Encrypt for ds-01. This 
may take a few minutes."
        }]}
 
 .. [#needOne] Either the ``key`` or the ``deliveryservice`` field must be 
provided. If both are provided, then they must match.
diff --git a/docs/source/api/v3/letsencrypt_autorenew.rst 
b/docs/source/api/v3/letsencrypt_autorenew.rst
index e858a9d..39d7a8d 100644
--- a/docs/source/api/v3/letsencrypt_autorenew.rst
+++ b/docs/source/api/v3/letsencrypt_autorenew.rst
@@ -34,29 +34,6 @@ No parameters available
 
 Response Structure
 ------------------
-:LetsEncryptExpirations: A list of objects with information regarding 
certificate expiration for all Let's Encrypt certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
-
-:SelfSignedExpirations:  A list of objects with information regarding 
certificate expiration for all self signed certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
-
-:OtherExpirations:       A list of objects with information regarding 
certificate expiration for all other certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
 
 .. code-block:: http
        :caption: Response Example
@@ -64,24 +41,13 @@ Response Structure
        HTTP/1.1 200 OK
        Content-Type: application/json
 
-       { "response": {
-               "LetsEncryptExpirations": [
-                       {
-                               "XmlId":"demo2",
-                               "Version":1,
-                               "Expiration":"2020-08-18T13:53:06Z",
-                               "AuthType":"Lets Encrypt",
-                               "Error":null
-                       }
-               ],
-               "SelfSignedExpirations": [
-                       {
-                               "XmlId":"demo1",
-                               "Version":3,
-                               "Expiration":"2020-08-18T13:53:06Z",
-                               "AuthType":"Self Signed",
-                               "Error":null
-                       }
-               ],
-               "OtherExpirations":null
-       }}
+       { "alerts": [
+               {
+                       "text": "This endpoint is deprecated, please use 
letsencrypt/autorenew instead",
+                       "level": "warning"
+               },
+               {
+                       "text": "Beginning async call to renew certificates. 
This may take a few minutes.",
+                       "level": "success"
+               }
+       ]}
diff --git a/docs/source/api/v4/acme_autorenew.rst 
b/docs/source/api/v4/acme_autorenew.rst
new file mode 100644
index 0000000..bf65a0a
--- /dev/null
+++ b/docs/source/api/v4/acme_autorenew.rst
@@ -0,0 +1,49 @@
+..
+..
+.. Licensed under the Apache License, Version 2.0 (the "License");
+.. you may not use this file except in compliance with the License.
+.. You may obtain a copy of the License at
+..
+..     http://www.apache.org/licenses/LICENSE-2.0
+..
+.. Unless required by applicable law or agreed to in writing, software
+.. distributed under the License is distributed on an "AS IS" BASIS,
+.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.. See the License for the specific language governing permissions and
+.. limitations under the License.
+..
+
+.. _to-api-acnme-autorenew:
+
+******************
+``acme_autorenew``
+******************
+
+``POST``
+========
+Generates SSL certificates and private keys for all :term:`Delivery Services` 
that have certificates expiring within the configured time. This 
uses:abbr:`ACME (Automatic Certificate Management Environment)` or Let's 
Encrypt depending on the certificate.
+
+:Auth. Required: Yes
+:Roles Required: "admin" or "operations"
+:Response Type:  ``undefined``
+
+Request Structure
+-----------------
+No parameters available
+
+
+Response Structure
+------------------
+
+.. code-block:: http
+       :caption: Response Example
+
+       HTTP/1.1 202 Accepted
+       Content-Type: application/json
+
+       { "alerts": [
+               {
+                       "text": "Beginning async call to renew certificates. 
This may take a few minutes.",
+                       "level": "success"
+               }
+       ]}
diff --git 
a/docs/source/api/v4/deliveryservices_sslkeys_generate_letsencrypt.rst 
b/docs/source/api/v4/deliveryservices_sslkeys_generate_letsencrypt.rst
index 67fc9ae..ddeab1d 100644
--- a/docs/source/api/v4/deliveryservices_sslkeys_generate_letsencrypt.rst
+++ b/docs/source/api/v4/deliveryservices_sslkeys_generate_letsencrypt.rst
@@ -60,7 +60,7 @@ Response Structure
 
        { "alerts": [{
                "level": "success",
-               "text": "Beginning async call to Let's Encrypt for ds-01.  This 
may take a few minutes."
+               "text": "Beginning async call to Let's Encrypt for ds-01. This 
may take a few minutes."
        }]}
 
 .. [#needOne] Either the ``key`` or the ``deliveryservice`` field must be 
provided. If both are provided, then they must match.
diff --git a/docs/source/api/v4/letsencrypt_autorenew.rst 
b/docs/source/api/v4/letsencrypt_autorenew.rst
index 516d4bd..bf33acb 100644
--- a/docs/source/api/v4/letsencrypt_autorenew.rst
+++ b/docs/source/api/v4/letsencrypt_autorenew.rst
@@ -34,29 +34,6 @@ No parameters available
 
 Response Structure
 ------------------
-:LetsEncryptExpirations: A list of objects with information regarding 
certificate expiration for all Let's Encrypt certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
-
-:SelfSignedExpirations:  A list of objects with information regarding 
certificate expiration for all self signed certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
-
-:OtherExpirations:       A list of objects with information regarding 
certificate expiration for all other certificates
-
-       :XmlId:       The :term:`Delivery Service`'s uniquely identifying 
:ref:`ds-xmlid`
-       :Version:     An integer that defines the "version" of the key - which 
may be thought of as the sequential generation; that is, the higher the number 
the more recent the key
-       :Expiration:  The expiration date of the certificate for the 
:term:`Delivery Service` in :rfc:`3339` format
-       :AuthType:    The authority type of the certificate for the 
:term:`Delivery Service`
-       :Error:       Any errors received in the renewal process
 
 .. code-block:: http
        :caption: Response Example
@@ -64,24 +41,13 @@ Response Structure
        HTTP/1.1 200 OK
        Content-Type: application/json
 
-       { "response": {
-               "LetsEncryptExpirations": [
-                       {
-                               "XmlId":"demo2",
-                               "Version":1,
-                               "Expiration":"2020-08-18T13:53:06Z",
-                               "AuthType":"Lets Encrypt",
-                               "Error":null
-                       }
-               ],
-               "SelfSignedExpirations": [
-                       {
-                               "XmlId":"demo1",
-                               "Version":3,
-                               "Expiration":"2020-08-18T13:53:06Z",
-                               "AuthType":"Self Signed",
-                               "Error":null
-                       }
-               ],
-               "OtherExpirations":null
-       }}
+       { "alerts": [
+               {
+                       "text": "This endpoint is deprecated, please use 
letsencrypt/autorenew instead",
+                       "level": "warning"
+               },
+               {
+                       "text": "Beginning async call to renew certificates. 
This may take a few minutes.",
+                       "level": "success"
+               }
+       ]}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/config.sh 
b/infrastructure/cdn-in-a-box/traffic_ops/config.sh
index 9dc3f6c..1d1ccaa 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/config.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/config.sh
@@ -152,6 +152,10 @@ cat <<-EOF >/opt/traffic_ops/app/conf/cdn.conf
         "renew_days_before_expiration": 30,
         "environment": "staging"
     },
+    "acme_renewal": {
+        "summary_email": "",
+        "renew_days_before_expiration": 30
+    },
     "acme_accounts": [
         {
             "acme_provider" : "",
diff --git a/traffic_ops/app/conf/cdn.conf b/traffic_ops/app/conf/cdn.conf
index b6905ae..771d9ba 100644
--- a/traffic_ops/app/conf/cdn.conf
+++ b/traffic_ops/app/conf/cdn.conf
@@ -82,6 +82,10 @@
         "renew_days_before_expiration": 30,
         "environment": "production"
     },
+    "acme_renewal": {
+        "summary_email": "",
+        "renew_days_before_expiration": 30
+    },
     "acme_accounts": [
         {
             "acme_provider" : "",
diff --git a/traffic_ops/app/templates/send_mail/autorenewcerts_mail.html 
b/traffic_ops/app/templates/send_mail/autorenewcerts_mail.html
index c9fdab1..a68494a 100644
--- a/traffic_ops/app/templates/send_mail/autorenewcerts_mail.html
+++ b/traffic_ops/app/templates/send_mail/autorenewcerts_mail.html
@@ -56,6 +56,30 @@ td, th {
                </tbody>
                </table>
 
+               <h1>ACME Certificates Summary</h1>
+               <table>
+                       <thead>
+                       <tr>
+                               <th>XmlId</th>
+                               <th>Version</th>
+                               <th>Expiration</th>
+                               <th>AuthType</th>
+                               <th>Error</th>
+                       </tr>
+                       </thead>
+                       <tbody>
+                       {{range .AcmeExpirations}}
+                               <tr>
+                                       <td>{{.XmlId}}</td>
+                                       <td>{{.Version}}</td>
+                                       <td>{{.Expiration}}</td>
+                                       <td>{{.AuthType}}</td>
+                                       <td>{{.Error}}</td>
+                               </tr>
+                       {{end}}
+                       </tbody>
+               </table>
+
                <h1>Self Signed Certificates Summary</h1>
                <table>
                <thead>
diff --git a/traffic_ops/traffic_ops_golang/config/config.go 
b/traffic_ops/traffic_ops_golang/config/config.go
index 7f95a33..521f5e8 100644
--- a/traffic_ops/traffic_ops_golang/config/config.go
+++ b/traffic_ops/traffic_ops_golang/config/config.go
@@ -48,6 +48,7 @@ type Config struct {
        SMTP                   *ConfigSMTP `json:"smtp"`
        ConfigPortal           `json:"portal"`
        ConfigLetsEncrypt      `json:"lets_encrypt"`
+       ConfigAcmeRenewal      `json:"acme_renewal"`
        AcmeAccounts           []ConfigAcmeAccount `json:"acme_accounts"`
        DB                     ConfigDatabase      `json:"db"`
        Secrets                []string            `json:"secrets"`
@@ -159,6 +160,12 @@ type ConfigLetsEncrypt struct {
        Environment               string `json:"environment"`
 }
 
+// ConfigAcmeRenewal continas configuration information for automated ACME 
renewals.
+type ConfigAcmeRenewal struct {
+       SummaryEmail              string `json:"summary_email"`
+       RenewDaysBeforeExpiration int    `json:"renew_days_before_expiration"`
+}
+
 // ConfigAcmeAccount contains all account information for a single ACME 
provider to be registered with External Account Binding
 type ConfigAcmeAccount struct {
        AcmeProvider string `json:"acme_provider"`
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/acme.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/acme.go
index 35476b4..30cdb49 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/acme.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/acme.go
@@ -49,6 +49,7 @@ import (
 
 const validAccountStatus = "valid"
 
+// RenewAcmeCertificate renews the SSL certificate for a delivery service if 
possible through ACME protocol.
 func RenewAcmeCertificate(w http.ResponseWriter, r *http.Request) {
        inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"xmlid"}, nil)
        if userErr != nil || sysErr != nil {
@@ -132,14 +133,13 @@ func renewAcmeCerts(cfg *config.Config, dsName string, 
ctx context.Context, curr
                return nil, errors.New("decoding cert for XMLID " + dsName + " 
: " + err.Error()), http.StatusInternalServerError
        }
 
-       acmeAccount := getAcmeAccountConfig(cfg, keyObj.AuthType)
+       acmeAccount := GetAcmeAccountConfig(cfg, keyObj.AuthType)
        if acmeAccount == nil {
                return nil, errors.New("No acme account information in cdn.conf 
for " + keyObj.AuthType), http.StatusInternalServerError
        }
 
        client, err := GetAcmeClient(acmeAccount, userTx, db)
        if err != nil {
-               log.Errorf(dsName+": Error getting acme client: %s", 
err.Error())
                api.CreateChangeLogRawTx(api.ApiChange, "DS: "+dsName+", ID: 
"+strconv.Itoa(*dsID)+", ACTION: FAILED to add SSL keys with 
"+acmeAccount.AcmeProvider, currentUser, logTx)
                return nil, errors.New("getting acme client: " + err.Error()), 
http.StatusInternalServerError
        }
@@ -192,7 +192,8 @@ func renewAcmeCerts(cfg *config.Config, dsName string, ctx 
context.Context, curr
        return nil, nil, http.StatusOK
 }
 
-func getAcmeAccountConfig(cfg *config.Config, acmeProvider string) 
*config.ConfigAcmeAccount {
+// GetAcmeAccountConfig returns the ACME account information from cdn.conf for 
a given provider.
+func GetAcmeAccountConfig(cfg *config.Config, acmeProvider string) 
*config.ConfigAcmeAccount {
        for _, acmeCfg := range cfg.AcmeAccounts {
                if acmeCfg.AcmeProvider == acmeProvider {
                        return &acmeCfg
@@ -212,7 +213,7 @@ func getDSIdAndVersionFromName(db *sqlx.DB, xmlId string) 
(*int, *int64, error)
        return &dsID, &certVersion, nil
 }
 
-// GetAcmeClient uses the ACME account information in either cdn.conf or the 
database to create and register an ACME client
+// GetAcmeClient uses the ACME account information in either cdn.conf or the 
database to create and register an ACME client.
 func GetAcmeClient(acmeAccount *config.ConfigAcmeAccount, userTx *sql.Tx, db 
*sqlx.DB) (*lego.Client, error) {
        if acmeAccount.UserEmail == "" {
                log.Errorf("An email address must be provided to use ACME with 
%v", acmeAccount.AcmeProvider)
@@ -321,6 +322,7 @@ func GetAcmeClient(acmeAccount *config.ConfigAcmeAccount, 
userTx *sql.Tx, db *sq
        return client, nil
 }
 
+// ConvertPrivateKeyToKeyPem converts an rsa.PrivateKey to be PEM encoded.
 func ConvertPrivateKeyToKeyPem(userPrivateKey *rsa.PrivateKey) ([]byte, error) 
{
        userKeyDer := x509.MarshalPKCS1PrivateKey(userPrivateKey)
        if userKeyDer == nil {
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go
index 287409d..cea7e55 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go
@@ -52,27 +52,42 @@ type DsExpirationInfo struct {
 type ExpirationSummary struct {
        LetsEncryptExpirations []DsExpirationInfo
        SelfSignedExpirations  []DsExpirationInfo
+       AcmeExpirations        []DsExpirationInfo
        OtherExpirations       []DsExpirationInfo
 }
 
 const emailTemplateFile = 
"/opt/traffic_ops/app/templates/send_mail/autorenewcerts_mail.html"
+const API_ACME_AUTORENEW = "acme_autorenew"
 
+// RenewCertificatesDeprecated renews all SSL certificates that are expiring 
within a certain time limit with a deprecation alert.
+//// This will renew Let's Encrypt and ACME certificates.
+func RenewCertificatesDeprecated(w http.ResponseWriter, r *http.Request) {
+       renewCertificates(w, r, true)
+}
+
+// RenewCertificates renews all SSL certificates that are expiring within a 
certain time limit.
+// This will renew Let's Encrypt and ACME certificates.
 func RenewCertificates(w http.ResponseWriter, r *http.Request) {
+       renewCertificates(w, r, false)
+}
+
+func renewCertificates(w http.ResponseWriter, r *http.Request, deprecated 
bool) {
+       deprecation := util.StrPtr(API_ACME_AUTORENEW)
        inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
        if userErr != nil || sysErr != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               api.HandleErrOptionalDeprecation(w, r, inf.Tx.Tx, errCode, 
userErr, sysErr, deprecated, deprecation)
                return
        }
        defer inf.Close()
 
        if inf.Config.RiakEnabled == false {
-               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
errors.New("the Riak service is unavailable"), errors.New("getting SSL keys 
from Riak by xml id: Riak is not configured"))
+               api.HandleErrOptionalDeprecation(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, errors.New("the Riak service is unavailable"), 
errors.New("getting SSL keys from Riak by xml id: Riak is not configured"), 
deprecated, deprecation)
                return
        }
 
        rows, err := inf.Tx.Tx.Query(`SELECT xml_id, ssl_key_version FROM 
deliveryservice WHERE ssl_key_version != 0`)
        if err != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, err)
+               api.HandleErrOptionalDeprecation(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, err, deprecated, deprecation)
                return
        }
        defer rows.Close()
@@ -91,7 +106,16 @@ func RenewCertificates(w http.ResponseWriter, r 
*http.Request) {
 
        go RunAutorenewal(existingCerts, inf.Config, ctx, inf.User)
 
-       api.WriteRespAlert(w, r, tc.SuccessLevel, "Beginning async call to 
renew Let's Encrypt certificates.  This may take a few minutes.")
+       var alerts tc.Alerts
+       if deprecated {
+               alerts.AddAlerts(api.CreateDeprecationAlerts(deprecation))
+       }
+
+       alerts.AddAlert(tc.Alert{
+               Text:  "Beginning async call to renew certificates. This may 
take a few minutes.",
+               Level: tc.SuccessLevel.String(),
+       })
+       api.WriteAlerts(w, r, http.StatusAccepted, alerts)
 
 }
 func RunAutorenewal(existingCerts []ExistingCerts, cfg *config.Config, ctx 
context.Context, currentUser *auth.CurrentUser) {
@@ -151,8 +175,11 @@ func RunAutorenewal(existingCerts []ExistingCerts, cfg 
*config.Config, ctx conte
                        return
                }
 
-               // Renew only certificates within configured limit
-               if expiration.After(time.Now().Add(time.Hour * 24 * 
time.Duration(cfg.ConfigLetsEncrypt.RenewDaysBeforeExpiration))) {
+               // Renew only certificates within configured limit. Default is 
30 days.
+               if cfg.ConfigAcmeRenewal.RenewDaysBeforeExpiration == 0 {
+                       cfg.ConfigAcmeRenewal.RenewDaysBeforeExpiration = 30
+               }
+               if expiration.After(time.Now().Add(time.Hour * 24 * 
time.Duration(cfg.ConfigAcmeRenewal.RenewDaysBeforeExpiration))) {
                        continue
                }
 
@@ -183,12 +210,26 @@ func RunAutorenewal(existingCerts []ExistingCerts, cfg 
*config.Config, ctx conte
                } else if keyObj.AuthType == tc.SelfSignedCertAuthType {
                        keysFound.SelfSignedExpirations = 
append(keysFound.SelfSignedExpirations, dsExpInfo)
                } else {
-                       keysFound.OtherExpirations = 
append(keysFound.OtherExpirations, dsExpInfo)
+                       acmeAccount := GetAcmeAccountConfig(cfg, 
keyObj.AuthType)
+                       if acmeAccount == nil {
+                               keysFound.OtherExpirations = 
append(keysFound.OtherExpirations, dsExpInfo)
+                       } else {
+                               userErr, sysErr, statusCode := 
renewAcmeCerts(cfg, keyObj.DeliveryService, ctx, currentUser)
+                               if userErr != nil {
+                                       dsExpInfo.Error = userErr
+                               } else if sysErr != nil {
+                                       dsExpInfo.Error = sysErr
+                               } else if statusCode != http.StatusOK {
+                                       dsExpInfo.Error = errors.New("Status 
code not 200: " + strconv.Itoa(statusCode))
+                               }
+                               keysFound.AcmeExpirations = 
append(keysFound.AcmeExpirations, dsExpInfo)
+                       }
+
                }
 
        }
 
-       if cfg.SMTP.Enabled && cfg.ConfigLetsEncrypt.SendExpEmail {
+       if cfg.SMTP.Enabled && cfg.ConfigAcmeRenewal.SummaryEmail != "" {
                errCode, userErr, sysErr := AlertExpiringCerts(keysFound, *cfg)
                if userErr != nil || sysErr != nil {
                        log.Errorf("cert autorenewal: sending email: errCode: 
%d userErr: %v sysErr: %v", errCode, userErr, sysErr)
@@ -200,12 +241,12 @@ func RunAutorenewal(existingCerts []ExistingCerts, cfg 
*config.Config, ctx conte
 
 func AlertExpiringCerts(certsFound ExpirationSummary, config config.Config) 
(int, error, error) {
        header := "From: " + config.ConfigTO.EmailFrom.String() + "\r\n" +
-               "To: " + config.ConfigLetsEncrypt.Email + "\r\n" +
+               "To: " + config.ConfigAcmeRenewal.SummaryEmail + "\r\n" +
                "MIME-version: 1.0;\r\n" +
                "Content-Type: text/html; charset=\"UTF-8\";\r\n" +
                "Subject: Certificate Expiration Summary\r\n\r\n"
 
-       return api.SendEmailFromTemplate(config, header, certsFound, 
emailTemplateFile, config.ConfigLetsEncrypt.Email)
+       return api.SendEmailFromTemplate(config, header, certsFound, 
emailTemplateFile, config.ConfigAcmeRenewal.SummaryEmail)
 }
 
 type ExistingCerts struct {
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/letsencryptcert.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/letsencryptcert.go
index 431f9ec..a062ef8 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/letsencryptcert.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/letsencryptcert.go
@@ -179,7 +179,7 @@ func GenerateLetsEncryptCertificates(w http.ResponseWriter, 
r *http.Request) {
 
        go GetLetsEncryptCertificates(inf.Config, req, ctx, inf.User)
 
-       api.WriteRespAlert(w, r, tc.SuccessLevel, "Beginning async call to 
Let's Encrypt for "+*req.DeliveryService+".  This may take a few minutes.")
+       api.WriteRespAlert(w, r, tc.SuccessLevel, "Beginning async call to 
Let's Encrypt for "+*req.DeliveryService+". This may take a few minutes.")
 
 }
 
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index ef76227..a3c813a 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -153,6 +153,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
 
                //Delivery service ACME
                {api.Version{4, 0}, http.MethodPost, 
`deliveryservices/xmlId/{xmlid}/sslkeys/renew$`, 
deliveryservice.RenewAcmeCertificate, auth.PrivLevelOperations, Authenticated, 
nil, 2534390573, noPerlBypass},
+               {api.Version{4, 0}, http.MethodPost, `acme_autorenew/?$`, 
deliveryservice.RenewCertificates, auth.PrivLevelOperations, Authenticated, 
nil, 2534390574, noPerlBypass},
 
                // API Capability
                {api.Version{4, 0}, http.MethodGet, `api_capabilities/?$`, 
apicapability.GetAPICapabilitiesHandler, auth.PrivLevelReadOnly, Authenticated, 
nil, 48132065893, noPerlBypass},
@@ -505,7 +506,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
                //Delivery service LetsEncrypt
                {api.Version{4, 0}, http.MethodPost, 
`deliveryservices/sslkeys/generate/letsencrypt/?$`, 
deliveryservice.GenerateLetsEncryptCertificates, auth.PrivLevelOperations, 
Authenticated, nil, 4534390523, noPerlBypass},
                {api.Version{4, 0}, http.MethodGet, 
`letsencrypt/dnsrecords/?$`, deliveryservice.GetDnsChallengeRecords, 
auth.PrivLevelOperations, Authenticated, nil, 4534390553, noPerlBypass},
-               {api.Version{4, 0}, http.MethodPost, 
`letsencrypt/autorenew/?$`, deliveryservice.RenewCertificates, 
auth.PrivLevelOperations, Authenticated, nil, 4534390563, noPerlBypass},
+               {api.Version{4, 0}, http.MethodPost, 
`letsencrypt/autorenew/?$`, deliveryservice.RenewCertificatesDeprecated, 
auth.PrivLevelOperations, Authenticated, nil, 4534390563, noPerlBypass},
 
                {api.Version{4, 0}, http.MethodGet, 
`deliveryservices/{id}/health/?$`, deliveryservice.GetHealth, 
auth.PrivLevelReadOnly, Authenticated, nil, 42345901013, noPerlBypass},
 

Reply via email to