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 9669824  Cert Expirations Traffic Ops Endpoint (#6338)
9669824 is described below

commit 9669824f6ce329ba021d5b7ab11ca3f146e9dc04
Author: mattjackson220 <[email protected]>
AuthorDate: Thu Dec 16 13:12:48 2021 -0700

    Cert Expirations Traffic Ops Endpoint (#6338)
    
    * updated database and added script to pull expiration info from cert data
    
    * added API endpoint to show SSL cert expiration information
    
    * added int check on days parameter
    
    * fixed days param check
    
    * removed old files from previous PR
    
    * updated per comments
    
    * added trailing / ability to new endpoint
    
    * updated per comments
    
    * removed DS ID from API response
    
    * updated per comments
    
    * added migration to make provider not NULL
    
    * updated TO for non-null provider
---
 docs/source/api/v4/sslkey_expirations.rst          | 90 ++++++++++++++++++++++
 lib/go-tc/deliveryservice_ssl_keys.go              |  9 +++
 ...2021121011532000_set_provider_not_null.down.sql | 20 +++++
 .../2021121011532000_set_provider_not_null.up.sql  | 23 ++++++
 .../deliveryservice/autorenewcerts.go              |  2 +-
 .../traffic_ops_golang/deliveryservice/keys.go     | 32 +++++++-
 traffic_ops/traffic_ops_golang/routing/routes.go   |  3 +
 .../trafficvault/backends/disabled/disabled.go     |  4 +
 .../trafficvault/backends/postgres/postgres.go     | 69 ++++++++++++++++-
 .../trafficvault/backends/riaksvc/riak.go          |  4 +
 .../trafficvault/trafficvault.go                   |  2 +
 11 files changed, 253 insertions(+), 5 deletions(-)

diff --git a/docs/source/api/v4/sslkey_expirations.rst 
b/docs/source/api/v4/sslkey_expirations.rst
new file mode 100644
index 0000000..f299c87
--- /dev/null
+++ b/docs/source/api/v4/sslkey_expirations.rst
@@ -0,0 +1,90 @@
+..
+..
+.. 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-sslkey_expirations:
+
+**********************
+``sslkey_expirations``
+**********************
+
+``GET``
+=======
+Retrieves SSL certificate expiration information.
+
+:Auth. Required: Yes
+:Roles Required: "admin"
+:Permissions Required: ACME:READ
+:Response Type:  Array
+
+Request Structure
+-----------------
+.. table:: Request Query Parameters
+
+       
+-------------------+----------+--------------------------------------------------------------------------------------------------------+
+       | Name              | Required | Description                            
                                                                |
+       
+===================+==========+========================================================================================================+
+       | days              | no       | Return only the expiration information 
for SSL certificates expiring in the next given number of days. |
+       
+-------------------+----------+--------------------------------------------------------------------------------------------------------+
+
+.. code-block:: http
+       :caption: Request Example
+
+       GET /api/4.0/sslkey_expirations?days=30 HTTP/1.1
+       Host: trafficops.infra.ciab.test
+       Accept: */*
+       Connection: keep-alive
+       Cookie: mojolicious=...
+
+Response Structure
+------------------
+:deliveryservice:   The :ref:`ds-xmlid` for the :term:`Delivery Service` 
corresponding to this SSL certificate.
+:cdn:               The ID for the :abbr:`CDN (Content Delivery Network)` 
corresponding to this SSL certificate.
+:provider:          The provider of this SSL certificate, generally the name 
of the Certificate Authority or the :abbr:`ACME (Automatic Certificate 
Management Environment)` account.
+:expiration:        The expiration date of this SSL certificate.
+:federated:         A boolean indicating if this SSL certificate is use in a 
federated :term:`Delivery Service`.
+
+.. code-block:: http
+       :caption: Response Example
+
+       HTTP/1.1 200 OK
+       Access-Control-Allow-Credentials: true
+       Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, 
Accept, Set-Cookie, Cookie
+       Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE
+       Access-Control-Allow-Origin: *
+       Content-Encoding: gzip
+       Content-Type: application/json
+       Permissions-Policy: interest-cohort=()
+       Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 07 Jun 2021 22:52:20 
GMT; Max-Age=3600; HttpOnly
+       Vary: Accept-Encoding
+       X-Server-Name: traffic_ops_golang/
+       Date: Mon, 07 Jun 2021 21:52:20 GMT
+       Content-Length: 384
+
+       { "response": [
+               {
+                       "deliveryservice": "foo1",
+                       "cdn": "cdn1",
+                       "provider": "Self Signed",
+                       "expiration": "2022-08-02T15:38:06-06:00",
+                       "federated": false
+               },
+               {
+                       "deliveryservice": "foo2",
+                       "cdn": "cdn2",
+                       "provider": "Lets Encrypt",
+                       "expiration": "2022-07-12T12:14:00-06:00",
+                       "federated": true
+               }
+       ]}
diff --git a/lib/go-tc/deliveryservice_ssl_keys.go 
b/lib/go-tc/deliveryservice_ssl_keys.go
index f5acef4..e40173f 100644
--- a/lib/go-tc/deliveryservice_ssl_keys.go
+++ b/lib/go-tc/deliveryservice_ssl_keys.go
@@ -93,6 +93,15 @@ type DeliveryServiceSSLKeysV15 struct {
        Expiration time.Time `json:"expiration,omitempty"`
 }
 
+// SSLKeyExpirationInformation contains information about an SSL key's 
expiration.
+type SSLKeyExpirationInformation struct {
+       DeliveryService string    `json:"deliveryservice"`
+       CDN             string    `json:"cdn"`
+       Provider        string    `json:"provider"`
+       Expiration      time.Time `json:"expiration"`
+       Federated       bool      `json:"federated"`
+}
+
 // SSLKeyRequestFields contain metadata information for generating SSL keys for
 // Delivery Services through the Traffic Ops API. Specifically, they contain
 // everything except the manner in which the generated certificates should be
diff --git 
a/traffic_ops/app/db/trafficvault/migrations/2021121011532000_set_provider_not_null.down.sql
 
b/traffic_ops/app/db/trafficvault/migrations/2021121011532000_set_provider_not_null.down.sql
new file mode 100644
index 0000000..82fa54b
--- /dev/null
+++ 
b/traffic_ops/app/db/trafficvault/migrations/2021121011532000_set_provider_not_null.down.sql
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you 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.
+ */
+
+ALTER TABLE ONLY sslkey
+ALTER COLUMN provider
+DROP NOT NULL;
diff --git 
a/traffic_ops/app/db/trafficvault/migrations/2021121011532000_set_provider_not_null.up.sql
 
b/traffic_ops/app/db/trafficvault/migrations/2021121011532000_set_provider_not_null.up.sql
new file mode 100644
index 0000000..ac75a24
--- /dev/null
+++ 
b/traffic_ops/app/db/trafficvault/migrations/2021121011532000_set_provider_not_null.up.sql
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you 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.
+ */
+
+UPDATE sslkey
+SET provider = COALESCE(provider, '');
+
+ALTER TABLE ONLY sslkey
+ALTER COLUMN provider
+SET NOT NULL;
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go
index 2791c71..85aa3cf 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go
@@ -208,7 +208,7 @@ func RunAutorenewal(existingCerts []ExistingCerts, cfg 
*config.Config, ctx conte
                        continue
                }
 
-               expiration, _, err := 
parseExpirationAndSansFromCert([]byte(keyObj.Certificate.Crt), keyObj.Hostname)
+               expiration, _, err := 
ParseExpirationAndSansFromCert([]byte(keyObj.Certificate.Crt), keyObj.Hostname)
                if err != nil {
                        log.Errorf("cert autorenewal: %s: %s", ds.XmlId, 
err.Error())
                        dsExpInfo.XmlId = ds.XmlId
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/keys.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/keys.go
index 82af2dd..c834f55 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/keys.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/keys.go
@@ -136,6 +136,33 @@ func AddSSLKeys(w http.ResponseWriter, r *http.Request) {
        api.WriteResp(w, r, "Successfully added ssl keys for 
"+*req.DeliveryService)
 }
 
+// GetSSlKeyExpirationInformation gets expiration information for all SSL 
certificates.
+func GetSSlKeyExpirationInformation(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"days"})
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+       if !inf.Config.TrafficVaultEnabled {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting SSL keys expiration information from Traffic Vault: 
Traffic Vault is not configured"))
+               return
+       }
+
+       daysParam := 0
+       if days, ok := inf.IntParams["days"]; ok {
+               daysParam = days
+       }
+
+       expirationInfos, err := inf.Vault.GetExpirationInformation(inf.Tx.Tx, 
r.Context(), daysParam)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting SSL keys expiration information from Traffic Vault: 
"+err.Error()))
+               return
+       }
+
+       api.WriteResp(w, r, expirationInfos)
+}
+
 // GetSSLKeysByXMLID fetches the deliveryservice ssl keys by the specified 
xmlID. V15 includes expiration date.
 func GetSSLKeysByXMLID(w http.ResponseWriter, r *http.Request) {
        inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"xmlid"}, nil)
@@ -201,7 +228,7 @@ func getSslKeys(inf *api.APIInfo, ctx context.Context) 
(tc.DeliveryServiceSSLKey
                }
 
                if keyObj.Certificate.Crt != "" && keyObj.Expiration.IsZero() {
-                       exp, sans, err := 
parseExpirationAndSansFromCert([]byte(parsedCert.Crt), keyObj.Hostname)
+                       exp, sans, err := 
ParseExpirationAndSansFromCert([]byte(parsedCert.Crt), keyObj.Hostname)
                        if err != nil {
                                return tc.DeliveryServiceSSLKeysV4{}, 
errors.New(xmlID + ": " + err.Error())
                        }
@@ -213,7 +240,8 @@ func getSslKeys(inf *api.APIInfo, ctx context.Context) 
(tc.DeliveryServiceSSLKey
        return keyObj, nil
 }
 
-func parseExpirationAndSansFromCert(cert []byte, commonName string) 
(time.Time, []string, error) {
+// ParseExpirationAndSansFromCert returns the expiration and SANs from a 
certificate.
+func ParseExpirationAndSansFromCert(cert []byte, commonName string) 
(time.Time, []string, error) {
        block, _ := pem.Decode(cert)
        if block == nil {
                return time.Time{}, []string{}, errors.New("Error decoding cert 
to parse expiration")
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index c6112c2..710220d 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -130,6 +130,9 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                 * 4.x API
                 */
 
+               // SSL Keys
+               {api.Version{Major: 4, Minor: 0}, http.MethodGet, 
`sslkey_expirations/?$`, deliveryservice.GetSSlKeyExpirationInformation, 
auth.PrivLevelAdmin, []string{"SSL-KEY-EXPIRATION:READ"}, Authenticated, nil, 
41357729075},
+
                // CDN lock
                {api.Version{Major: 4, Minor: 0}, http.MethodGet, 
`cdn_locks/?$`, cdn_lock.Read, auth.PrivLevelReadOnly, []string{"CDN:READ"}, 
Authenticated, nil, 4134390561},
                {api.Version{Major: 4, Minor: 0}, http.MethodPost, 
`cdn_locks/?$`, cdn_lock.Create, auth.PrivLevelOperations, 
[]string{"CDN-LOCK:CREATE", "CDN:READ"}, Authenticated, nil, 4134390562},
diff --git 
a/traffic_ops/traffic_ops_golang/trafficvault/backends/disabled/disabled.go 
b/traffic_ops/traffic_ops_golang/trafficvault/backends/disabled/disabled.go
index d18d68b..c79f566 100644
--- a/traffic_ops/traffic_ops_golang/trafficvault/backends/disabled/disabled.go
+++ b/traffic_ops/traffic_ops_golang/trafficvault/backends/disabled/disabled.go
@@ -45,6 +45,10 @@ func (d *Disabled) GetDeliveryServiceSSLKeys(xmlID string, 
version string, tx *s
        return tc.DeliveryServiceSSLKeysV15{}, false, disabledErr
 }
 
+func (d *Disabled) GetExpirationInformation(tx *sql.Tx, ctx context.Context, 
days int) ([]tc.SSLKeyExpirationInformation, error) {
+       return []tc.SSLKeyExpirationInformation{}, disabledErr
+}
+
 func (d *Disabled) PutDeliveryServiceSSLKeys(key tc.DeliveryServiceSSLKeys, tx 
*sql.Tx, ctx context.Context) error {
        return disabledErr
 }
diff --git 
a/traffic_ops/traffic_ops_golang/trafficvault/backends/postgres/postgres.go 
b/traffic_ops/traffic_ops_golang/trafficvault/backends/postgres/postgres.go
index 47bd2dd..b03b48d 100644
--- a/traffic_ops/traffic_ops_golang/trafficvault/backends/postgres/postgres.go
+++ b/traffic_ops/traffic_ops_golang/trafficvault/backends/postgres/postgres.go
@@ -33,6 +33,7 @@ import (
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
        "github.com/apache/trafficcontrol/lib/go-util"
+       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice"
        
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault"
 
        validation "github.com/go-ozzo/ozzo-validation"
@@ -156,6 +157,61 @@ func (p *Postgres) GetDeliveryServiceSSLKeys(xmlID string, 
version string, tx *s
        return sslKey, true, nil
 }
 
+// GetExpirationInformation returns the expiration information for all SSL 
Keys.
+func (p *Postgres) GetExpirationInformation(tx *sql.Tx, ctx context.Context, 
days int) ([]tc.SSLKeyExpirationInformation, error) {
+       tvTx, dbCtx, cancelFunc, err := p.beginTransaction(ctx)
+       if err != nil {
+               return []tc.SSLKeyExpirationInformation{}, err
+       }
+       defer p.commitTransaction(tvTx, dbCtx, cancelFunc)
+
+       var fedList []string
+       fedRows, err := tx.Query("SELECT DISTINCT(ds.xml_id) FROM 
federation_deliveryservice AS fd JOIN deliveryservice AS ds ON ds.id = 
fd.deliveryservice")
+       if err != nil {
+               return []tc.SSLKeyExpirationInformation{}, err
+       }
+       defer fedRows.Close()
+
+       for fedRows.Next() {
+               var fedString string
+               if err = fedRows.Scan(&fedString); err != nil {
+                       return []tc.SSLKeyExpirationInformation{}, err
+               }
+               fedList = append(fedList, fedString)
+       }
+
+       query := "SELECT deliveryservice, cdn, provider, expiration FROM sslkey 
WHERE version='latest'"
+
+       if days != 0 {
+               query = query + fmt.Sprintf(" AND expiration <= (now() + '%d 
days'::interval)", days)
+       }
+
+       expirationInfos := []tc.SSLKeyExpirationInformation{}
+
+       rows, err := tvTx.Query(query)
+       if err != nil {
+               return []tc.SSLKeyExpirationInformation{}, err
+       }
+       defer rows.Close()
+
+       for rows.Next() {
+               var expirationInfo tc.SSLKeyExpirationInformation
+               if err = rows.Scan(&expirationInfo.DeliveryService, 
&expirationInfo.CDN, &expirationInfo.Provider, &expirationInfo.Expiration); err 
!= nil {
+                       return []tc.SSLKeyExpirationInformation{}, err
+               }
+               expirationInfo.Federated = false
+               for _, fed := range fedList {
+                       if fed == expirationInfo.DeliveryService {
+                               expirationInfo.Federated = true
+                       }
+               }
+
+               expirationInfos = append(expirationInfos, expirationInfo)
+       }
+
+       return expirationInfos, nil
+}
+
 // PutDeliveryServiceSSLKeys stores the given SSL keys for a delivery service.
 func (p *Postgres) PutDeliveryServiceSSLKeys(key tc.DeliveryServiceSSLKeys, tx 
*sql.Tx, ctx context.Context) error {
        tvTx, dbCtx, cancelFunc, err := p.beginTransaction(ctx)
@@ -178,11 +234,20 @@ func (p *Postgres) PutDeliveryServiceSSLKeys(key 
tc.DeliveryServiceSSLKeys, tx *
 
        encryptedKey, err := util.AESEncrypt(keyJSON, p.aesKey)
        if err != nil {
-               return errors.New("encrypting keys: " + err.Error())
+               return fmt.Errorf("encrypting keys: %w", err)
+       }
+
+       err = deliveryservice.Base64DecodeCertificate(&key.Certificate)
+       if err != nil {
+               return fmt.Errorf("decoding SSL keys, %w", err)
+       }
+       expiration, _, err := 
deliveryservice.ParseExpirationAndSansFromCert([]byte(key.Certificate.Crt), 
key.Hostname)
+       if err != nil {
+               return fmt.Errorf("parsing expiration from certificate: %w", 
err)
        }
 
        // insert the new ssl keys now
-       res, err := tvTx.Exec("INSERT INTO sslkey (deliveryservice, data, cdn, 
version) VALUES ($1, $2, $3, $4), ($5, $6, $7, $8)", key.DeliveryService, 
encryptedKey, key.CDN, strconv.FormatInt(int64(key.Version), 10), 
key.DeliveryService, encryptedKey, key.CDN, latestVersion)
+       res, err := tvTx.Exec("INSERT INTO sslkey (deliveryservice, data, cdn, 
version, provider, expiration) VALUES ($1, $2, $3, $4, $5, $6), ($7, $8, $9, 
$10, $11, $12)", key.DeliveryService, encryptedKey, key.CDN, 
strconv.FormatInt(int64(key.Version), 10), key.AuthType, expiration, 
key.DeliveryService, encryptedKey, key.CDN, latestVersion, key.AuthType, 
expiration)
        if err != nil {
                e := checkErrWithContext("Traffic Vault PostgreSQL: executing 
INSERT SSL Key query", err, ctx.Err())
                return e
diff --git 
a/traffic_ops/traffic_ops_golang/trafficvault/backends/riaksvc/riak.go 
b/traffic_ops/traffic_ops_golang/trafficvault/backends/riaksvc/riak.go
index e7fb923..0daa48b 100644
--- a/traffic_ops/traffic_ops_golang/trafficvault/backends/riaksvc/riak.go
+++ b/traffic_ops/traffic_ops_golang/trafficvault/backends/riaksvc/riak.go
@@ -44,6 +44,10 @@ func (r *Riak) GetDeliveryServiceSSLKeys(xmlID string, 
version string, tx *sql.T
        return getDeliveryServiceSSLKeysObjV15(xmlID, version, tx, 
&r.cfg.AuthOptions, &r.cfg.Port)
 }
 
+func (r *Riak) GetExpirationInformation(tx *sql.Tx, ctx context.Context, days 
int) ([]tc.SSLKeyExpirationInformation, error) {
+       return []tc.SSLKeyExpirationInformation{}, errors.New("Not implemented 
for this Traffic Vault backend.")
+}
+
 func (r *Riak) PutDeliveryServiceSSLKeys(key tc.DeliveryServiceSSLKeys, tx 
*sql.Tx, ctx context.Context) error {
        return putDeliveryServiceSSLKeysObj(key, tx, &r.cfg.AuthOptions, 
&r.cfg.Port)
 }
diff --git a/traffic_ops/traffic_ops_golang/trafficvault/trafficvault.go 
b/traffic_ops/traffic_ops_golang/trafficvault/trafficvault.go
index 08031d8..9b58a90 100644
--- a/traffic_ops/traffic_ops_golang/trafficvault/trafficvault.go
+++ b/traffic_ops/traffic_ops_golang/trafficvault/trafficvault.go
@@ -42,6 +42,8 @@ type TrafficVault interface {
        // the delivery service identified by the given xmlID. If version is 
empty,
        // the implementation should return the latest version.
        GetDeliveryServiceSSLKeys(xmlID string, version string, tx *sql.Tx, ctx 
context.Context) (tc.DeliveryServiceSSLKeysV15, bool, error)
+       // GetExpirationInformation retrieves the SSL key expiration 
information for all delivery services.
+       GetExpirationInformation(tx *sql.Tx, ctx context.Context, days int) 
([]tc.SSLKeyExpirationInformation, error)
        // PutDeliveryServiceSSLKeys stores the given SSL keys for a delivery 
service.
        PutDeliveryServiceSSLKeys(key tc.DeliveryServiceSSLKeys, tx *sql.Tx, 
ctx context.Context) error
        // DeleteDeliveryServiceSSLKeys removes the SSL keys of the given 
version (or latest

Reply via email to