You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by oc...@apache.org on 2021/12/16 20:12:58 UTC

[trafficcontrol] branch master updated: Cert Expirations Traffic Ops Endpoint (#6338)

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 <33...@users.noreply.github.com>
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