You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ra...@apache.org on 2019/02/27 23:48:52 UTC
[trafficcontrol] branch master updated: Add TO deleting old DS
certs on snapshot (#3333)
This is an automated email from the ASF dual-hosted git repository.
rawlin 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 b6d2c0d Add TO deleting old DS certs on snapshot (#3333)
b6d2c0d is described below
commit b6d2c0d9d7ddde7d576796b2595b6b9c960b8207
Author: Robert Butts <ro...@users.noreply.github.com>
AuthorDate: Wed Feb 27 16:48:47 2019 -0700
Add TO deleting old DS certs on snapshot (#3333)
* Add TO deleting old DS certs on snapshot
* fix select race
* Add docs for snapshot deleting old certs
---
CHANGELOG.md | 1 +
docs/source/admin/traffic_ops/using.rst | 1 +
docs/source/api/cdns_id_snapshot.rst | 2 +
docs/source/api/snapshot_name.rst | 2 +
.../traffic_ops_golang/cdn/dnssecrefresh.go | 4 +
traffic_ops/traffic_ops_golang/crconfig/handler.go | 32 +++-
.../traffic_ops_golang/dbhelpers/db_helpers.go | 60 ++++++
.../deliveryservice/deleteoldcerts.go | 203 +++++++++++++++++++++
.../traffic_ops_golang/deliveryservice/keys.go | 6 +-
traffic_ops/traffic_ops_golang/riaksvc/dsutil.go | 54 +++++-
.../traffic_ops_golang/riaksvc/riak_services.go | 18 +-
11 files changed, 370 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43455c4..4e4bd37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Adds the DS Record text to the cdn dnsseckeys endpoint in 1.4.
- Added monitoring.json snapshotting. This stores the monitoring json in the same table as the crconfig snapshot. Snapshotting is now required in order to push out monitoring changes.
- To traffic_ops_ort.pl added the ability to handle ##OVERRIDE## delivery service ANY_MAP raw remap text to replace and comment out a base delivery service remap rules. THIS IS A TEMPORARY HACK until versioned delivery services are implemented.
+- Snapshotting the CRConfig now deletes HTTPS certificates in Riak for delivery services which have been deleted in Traffic Ops.
### Changed
- Traffic Ops Golang Endpoints
diff --git a/docs/source/admin/traffic_ops/using.rst b/docs/source/admin/traffic_ops/using.rst
index 8bc38ef..8fd38c4 100644
--- a/docs/source/admin/traffic_ops/using.rst
+++ b/docs/source/admin/traffic_ops/using.rst
@@ -1202,6 +1202,7 @@ To create a new snapshot, use the *Tools > Snapshot CRConfig* menu:
#. When the This will push out a new CRConfig.json. Are you sure? window opens, click **OK**.
#. The "Successfully wrote CRConfig.json!" window opens, click **OK**.
+.. Note:: Snapshotting the CDN also deletes all HTTPS certificates for every :term:`Delivery Service` which has been deleted since the last :term:`Snapshot`.
.. index::
Invalidate Content
diff --git a/docs/source/api/cdns_id_snapshot.rst b/docs/source/api/cdns_id_snapshot.rst
index fa140a9..b7076de 100644
--- a/docs/source/api/cdns_id_snapshot.rst
+++ b/docs/source/api/cdns_id_snapshot.rst
@@ -25,6 +25,8 @@
=======
Performs a CDN snapshot. Effectively, this propagates the new *configuration* of the CDN to its *operating state*, which replaces the output of the :ref:`to-api-cdns-name-snapshot` endpoint with the output of the :ref:`to-api-cdns-name-snapshot-new` endpoint.
+.. Note:: Snapshotting the CDN also deletes all HTTPS certificates for every :term:`Delivery Service` which has been deleted since the last CDN :term:`Snapshot`.
+
:Auth. Required: Yes
:Roles Required: "admin" or "operations"
:Response Type: ``undefined``
diff --git a/docs/source/api/snapshot_name.rst b/docs/source/api/snapshot_name.rst
index d47c583..0141bf1 100644
--- a/docs/source/api/snapshot_name.rst
+++ b/docs/source/api/snapshot_name.rst
@@ -23,6 +23,8 @@
=======
Performs a CDN snapshot. Effectively, this propagates the new *configuration* of the CDN to its *operating state*, which replaces the output of the :ref:`to-api-cdns-name-snapshot` endpoint with the output of the :ref:`to-api-cdns-name-snapshot-new` endpoint.
+.. Note:: Snapshotting the CDN also deletes all HTTPS certificates for every :term:`Delivery Service` which has been deleted since the last :term:`Snapshot`.
+
:Auth. Required: Yes
:Roles Required: "admin" or "operations"
:Response Type: ``undefined``
diff --git a/traffic_ops/traffic_ops_golang/cdn/dnssecrefresh.go b/traffic_ops/traffic_ops_golang/cdn/dnssecrefresh.go
index 6eefeb8..d8ae61a 100644
--- a/traffic_ops/traffic_ops_golang/cdn/dnssecrefresh.go
+++ b/traffic_ops/traffic_ops_golang/cdn/dnssecrefresh.go
@@ -45,17 +45,21 @@ func RefreshDNSSECKeys(w http.ResponseWriter, r *http.Request) {
noTx := (*sql.Tx)(nil) // make a variable instead of passing nil directly, to reduce copy-paste errors
if err != nil {
api.HandleErr(w, r, noTx, http.StatusInternalServerError, nil, errors.New("RefresHDNSSECKeys getting db from context: "+err.Error()))
+ unsetInDNSSECKeyRefresh()
return
}
cfg, err := api.GetConfig(r.Context())
if err != nil {
api.HandleErr(w, r, noTx, http.StatusInternalServerError, nil, errors.New("RefresHDNSSECKeys getting config from context: "+err.Error()))
+ unsetInDNSSECKeyRefresh()
return
}
tx, err := db.Begin()
if err != nil {
api.HandleErr(w, r, noTx, http.StatusInternalServerError, nil, errors.New("RefresHDNSSECKeys beginning tx: "+err.Error()))
+ unsetInDNSSECKeyRefresh()
+ return
}
go doDNSSECKeyRefresh(tx, cfg) // doDNSSECKeyRefresh takes ownership of tx and MUST close it.
} else {
diff --git a/traffic_ops/traffic_ops_golang/crconfig/handler.go b/traffic_ops/traffic_ops_golang/crconfig/handler.go
index 99c66b3..6797128 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/handler.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/handler.go
@@ -29,6 +29,7 @@ import (
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/monitoring"
)
@@ -127,6 +128,12 @@ func SnapshotHandler(w http.ResponseWriter, r *http.Request) {
}
defer inf.Close()
+ db, err := api.GetDB(r.Context())
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("SnapshotHandler getting db from context: "+err.Error()))
+ return
+ }
+
cdn, ok := inf.Params["cdn"]
if !ok {
id, ok := inf.IntParams["id"]
@@ -163,6 +170,11 @@ func SnapshotHandler(w http.ResponseWriter, r *http.Request) {
return
}
+ if err := deliveryservice.DeleteOldCerts(db.DB, inf.Tx.Tx, inf.Config, tc.CDNName(cdn)); err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New(r.RemoteAddr+" snapshotting CRConfig and Monitoring: starting old certificate deletion job: "+err.Error()))
+ return
+ }
+
api.CreateChangeLogRawTx(api.ApiChange, "Snapshot of CRConfig and Monitor performed for "+cdn, inf.User, inf.Tx.Tx)
api.WriteResp(w, r, "SUCCESS")
}
@@ -176,13 +188,21 @@ func SnapshotOldGUIHandler(w http.ResponseWriter, r *http.Request) {
}
defer inf.Close()
- crConfig, err := Make(inf.Tx.Tx, inf.Params["cdn"], inf.User.UserName, r.Host, r.URL.Path, inf.Config.Version, inf.Config.CRConfigUseRequestHost, inf.Config.CRConfigEmulateOldPath)
+ db, err := api.GetDB(r.Context())
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("SnapshotHandler getting db from context: "+err.Error()))
+ return
+ }
+
+ cdn := inf.Params["cdn"]
+
+ crConfig, err := Make(inf.Tx.Tx, cdn, inf.User.UserName, r.Host, r.URL.Path, inf.Config.Version, inf.Config.CRConfigUseRequestHost, inf.Config.CRConfigEmulateOldPath)
if err != nil {
writePerlHTMLErr(w, r, inf.Tx.Tx, errors.New(r.RemoteAddr+" making CRConfig: "+err.Error()), err)
return
}
- tm, err := monitoring.GetMonitoringJSON(inf.Tx.Tx, inf.Params["cdn"])
+ tm, err := monitoring.GetMonitoringJSON(inf.Tx.Tx, cdn)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New(r.RemoteAddr+" getting monitoring.json data: "+err.Error()))
return
@@ -192,7 +212,13 @@ func SnapshotOldGUIHandler(w http.ResponseWriter, r *http.Request) {
writePerlHTMLErr(w, r, inf.Tx.Tx, errors.New(r.RemoteAddr+" making CRConfig: "+err.Error()), err)
return
}
- api.CreateChangeLogRawTx(api.ApiChange, "Snapshot of CRConfig performed for "+inf.Params["cdn"], inf.User, inf.Tx.Tx)
+
+ if err := deliveryservice.DeleteOldCerts(db.DB, inf.Tx.Tx, inf.Config, tc.CDNName(cdn)); err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New(r.RemoteAddr+" old snapshotting CRConfig and Monitoring: starting old certificate deletion job: "+err.Error()))
+ return
+ }
+
+ api.CreateChangeLogRawTx(api.ApiChange, "Snapshot of CRConfig performed for "+cdn, inf.User, inf.Tx.Tx)
http.Redirect(w, r, "/tools/flash_and_close/"+url.PathEscape("Successfully wrote the CRConfig.json!"), http.StatusFound)
}
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index 4f15090..1ce496f 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -116,6 +116,28 @@ func AddTenancyCheck(where string, queryValues map[string]interface{}, tenantCol
return where, queryValues
}
+// CommitIf commits if doCommit is true at the time of execution.
+// This is designed as a defer helper.
+//
+// Example:
+//
+// tx, err := db.Begin()
+// txCommit := false
+// defer dbhelpers.CommitIf(tx, &txCommit)
+// if err := tx.Exec("select ..."); err != nil {
+// return errors.New("executing: " + err.Error())
+// }
+// txCommit = true
+// return nil
+//
+func CommitIf(tx *sql.Tx, doCommit *bool) {
+ if *doCommit {
+ tx.Commit()
+ } else {
+ tx.Rollback()
+ }
+}
+
// GetPrivLevelFromRoleID returns the priv_level associated with a role, whether it exists, and any error.
// This method exists on a temporary basis. After priv_level is fully deprecated and capabilities take over,
// this method will not only no longer be needed, but the corresponding new privilege check should be done
@@ -207,3 +229,41 @@ func GetCDNDomainFromName(tx *sql.Tx, cdnName tc.CDNName) (string, bool, error)
}
return domain, true, nil
}
+
+func GetCDNDSes(tx *sql.Tx, cdn tc.CDNName) (map[tc.DeliveryServiceName]struct{}, error) {
+ dses := map[tc.DeliveryServiceName]struct{}{}
+ qry := `SELECT xml_id from deliveryservice where cdn_id = (select id from cdn where name = $1)`
+ rows, err := tx.Query(qry, cdn)
+ if err != nil {
+ return nil, errors.New("querying: " + err.Error())
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ ds := tc.DeliveryServiceName("")
+ if err := rows.Scan(&ds); err != nil {
+ return nil, errors.New("scanning: " + err.Error())
+ }
+ dses[ds] = struct{}{}
+ }
+ return dses, nil
+}
+
+func GetCDNs(tx *sql.Tx) (map[tc.CDNName]struct{}, error) {
+ cdns := map[tc.CDNName]struct{}{}
+ qry := `SELECT name from cdn;`
+ rows, err := tx.Query(qry)
+ if err != nil {
+ return nil, errors.New("querying: " + err.Error())
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ cdn := tc.CDNName("")
+ if err := rows.Scan(&cdn); err != nil {
+ return nil, errors.New("scanning: " + err.Error())
+ }
+ cdns[cdn] = struct{}{}
+ }
+ return cdns, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deleteoldcerts.go b/traffic_ops/traffic_ops_golang/deliveryservice/deleteoldcerts.go
new file mode 100644
index 0000000..9447908
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deleteoldcerts.go
@@ -0,0 +1,203 @@
+package deliveryservice
+
+/*
+ * 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.
+ */
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/apache/trafficcontrol/lib/go-log"
+ "github.com/apache/trafficcontrol/lib/go-tc"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/riaksvc"
+
+ "github.com/basho/riak-go-client"
+)
+
+// DeleteOldCerts asynchronously deletes HTTPS certificates in Riak which have no corresponding delivery service in the database.
+//
+// Note the delivery service may still be in the CRConfig! Therefore, this should only be called immediately after a CRConfig Snapshot.
+//
+// This creates a goroutine, and immediately returns. It returns an error if there was an error preparing the delete routine, such as an error creating a db transaction.
+//
+// Note because it is asynchronous, this may return a nil error, but the asynchronous goroutine may error when fetching or deleting the certificates. If such an error occurs, it will be logged to the error log.
+//
+// If certificate deletion is already being processed by a goroutine, another delete will be queued, and this immediately returns nil. Only one delete will ever be queued.
+//
+func DeleteOldCerts(db *sql.DB, tx *sql.Tx, cfg *config.Config, cdn tc.CDNName) error {
+ if !cfg.RiakEnabled {
+ log.Infoln("deleting old delivery service certificates: Riak is not enabled, returning without cleaning up old certificates.")
+ return nil
+ }
+ if db == nil {
+ return errors.New("nil db")
+ }
+ if cfg == nil {
+ return errors.New("nil config")
+ }
+ startOldCertDeleter(db, tx, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second, cfg.RiakAuthOptions, cfg.RiakPort, cdn)
+ cleanupOldCertDeleters(tx)
+ return nil
+}
+
+// deleteOldDSCerts deletes the HTTPS certificates in Riak of delivery services which have been deleted in Traffic Ops.
+func deleteOldDSCerts(tx *sql.Tx, authOpts *riak.AuthOptions, riakPort *uint, cdn tc.CDNName) error {
+ dsKeys, err := riaksvc.GetCDNSSLKeysDSNames(tx, authOpts, riakPort, cdn)
+ if err != nil {
+ return errors.New("getting riak ds keys: " + err.Error())
+ }
+
+ dses, err := dbhelpers.GetCDNDSes(tx, cdn)
+ if err != nil {
+ return errors.New("getting ds names: " + err.Error())
+ }
+
+ successes := []string{}
+ failures := []string{}
+ for ds, riakKeys := range dsKeys {
+ if _, ok := dses[ds]; ok {
+ continue
+ }
+ for _, riakKey := range riakKeys {
+ err := riaksvc.DeleteDeliveryServicesSSLKey(tx, authOpts, riakPort, riakKey)
+ if err != nil {
+ log.Errorln("deleting Riak SSL keys for Delivery Service '" + string(ds) + "' key '" + riakKey + "': " + err.Error())
+ failures = append(failures, string(ds))
+ } else {
+ log.Infoln("Deleted Riak SSL keys for delivery service which has been deleted in the database '" + string(ds) + "' key '" + riakKey + "'")
+ successes = append(successes, string(ds))
+ }
+ }
+ }
+ if len(failures) > 0 {
+ return errors.New("successfully deleted Riak SSL keys for deleted dses [" + strings.Join(successes, ", ") + "], but failed to delete Riak SSL keys for [" + strings.Join(failures, ", ") + "]; see the error log for details")
+ }
+ return nil
+}
+
+// deleteOldDSCertsDB takes a db, and creates a transaction to pass to deleteOldDSCerts.
+func deleteOldDSCertsDB(db *sql.DB, dbTimeout time.Duration, riakOpts *riak.AuthOptions, riakPort *uint, cdn tc.CDNName) {
+ dbCtx, cancelTx := context.WithTimeout(context.Background(), dbTimeout)
+ tx, err := db.BeginTx(dbCtx, nil)
+ if err != nil {
+ log.Errorln("Old Cert Deleter Job: beginning tx: " + err.Error())
+ return
+ }
+ defer cancelTx()
+ txCommit := false
+ defer dbhelpers.CommitIf(tx, &txCommit)
+ if err := deleteOldDSCerts(tx, riakOpts, riakPort, cdn); err != nil {
+ log.Errorln("deleting old DS certificates: " + err.Error())
+ return
+ }
+ txCommit = true
+}
+
+var oldCertDeleters = OldCertDeleters{D: map[tc.CDNName]*OldCertDeleter{}}
+
+type OldCertDeleters struct {
+ D map[tc.CDNName]*OldCertDeleter
+ M sync.Mutex
+}
+
+// startOldCertDeleter tells the old cert deleter goroutine to start another delete job, creating the goroutine if it doesn't exist.
+func startOldCertDeleter(db *sql.DB, tx *sql.Tx, dbTimeout time.Duration, riakOpts *riak.AuthOptions, riakPort *uint, cdn tc.CDNName) {
+ oldCertDeleter := getOrCreateOldCertDeleter(cdn)
+ oldCertDeleter.Once.Do(func() {
+ go doOldCertDeleter(oldCertDeleter.Start, oldCertDeleter.Die, db, dbTimeout, riakOpts, riakPort, cdn)
+ })
+
+ select {
+ case oldCertDeleter.Start <- struct{}{}:
+ default:
+ }
+}
+
+func getOrCreateOldCertDeleter(cdn tc.CDNName) *OldCertDeleter {
+ oldCertDeleters.M.Lock()
+ defer oldCertDeleters.M.Unlock()
+ oldCertDeleter, ok := oldCertDeleters.D[cdn]
+ if !ok {
+ oldCertDeleter = newOldCertDeleter()
+ oldCertDeleters.D[cdn] = oldCertDeleter
+ }
+ return oldCertDeleter
+}
+
+// cleanupOldCertDeleters stops all cert deleter goroutines for CDNs which have been deleted in the database.
+// Any error is logged, but not returned.
+// This is designed to be called when starting a new cert deleter job, to clean up any old cert deleters from deleted CDNs.
+// This should only be called immediately when snapshotting, and immediately after startOldCertDeleter, because otherwise a cert deleter may be removed before it can delete old certs for a given CDN.
+func cleanupOldCertDeleters(tx *sql.Tx) {
+ cdns, err := dbhelpers.GetCDNs(tx) // (map[tc.CDNName]struct{}, error) {
+ if err != nil {
+ log.Errorln("cleanupOldCertDeleters: getting cdns: " + err.Error())
+ return
+ }
+
+ oldCertDeleters.M.Lock()
+ defer oldCertDeleters.M.Unlock()
+
+ for cdn, oldCertDeleter := range oldCertDeleters.D {
+ if _, ok := cdns[cdn]; ok {
+ continue
+ }
+ delete(oldCertDeleters.D, cdn)
+ select {
+ case oldCertDeleter.Die <- struct{}{}:
+ default:
+ }
+ }
+}
+
+type OldCertDeleter struct {
+ Start chan struct{}
+ Die chan struct{}
+ Once sync.Once
+}
+
+func newOldCertDeleter() *OldCertDeleter {
+ return &OldCertDeleter{
+ Start: make(chan struct{}, 1),
+ Die: make(chan struct{}, 1),
+ }
+}
+
+func doOldCertDeleter(do chan struct{}, die chan struct{}, db *sql.DB, dbTimeout time.Duration, riakOpts *riak.AuthOptions, riakPort *uint, cdn tc.CDNName) {
+ for {
+ select {
+ case <-do:
+ deleteOldDSCertsDB(db, dbTimeout, riakOpts, riakPort, cdn)
+ case <-die:
+ // Go selects aren't ordered, so double-check the do chan in case a race happened and a job came in at the same time as the die.
+ select {
+ case <-do:
+ deleteOldDSCertsDB(db, dbTimeout, riakOpts, riakPort, cdn)
+ default:
+ }
+ return
+ }
+ }
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/keys.go b/traffic_ops/traffic_ops_golang/deliveryservice/keys.go
index 365bbcb..7a82014 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/keys.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/keys.go
@@ -330,10 +330,10 @@ func verifyCertificate(certificate string, rootCA string) (string, bool, error)
block := &pem.Block{Type: "CERTIFICATE", Bytes: link.Raw}
pemEncodedChain += string(pem.EncodeToMemory(block))
}
-
- if len(pemEncodedChain) < 1 {
+
+ if len(pemEncodedChain) < 1 {
return "", false, errors.New("Invalid empty certicate chain in request")
- }
+ }
return pemEncodedChain, false, nil
}
diff --git a/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go b/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go
index 730e5b1..b99f0c8 100644
--- a/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go
+++ b/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go
@@ -200,6 +200,19 @@ func DeleteDSSSLKeys(tx *sql.Tx, authOpts *riak.AuthOptions, riakPort *uint, xml
return err
}
+// DeleteDeliveryServicesSSLKey deletes a Delivery Service SSL key.
+// This should almost never be used directly, prefer DeleteDSSSLKeys instead.
+// This should only be used to delete keys, which may not conform to the MakeDSSSLKeyKey format. For example when deleting all keys on a delivery service, and some may have been created manually outside Traffic Ops, or are otherwise malformed.
+func DeleteDeliveryServicesSSLKey(tx *sql.Tx, authOpts *riak.AuthOptions, riakPort *uint, key string) error {
+ err := WithCluster(tx, authOpts, riakPort, func(cluster StorageCluster) error {
+ if err := DeleteObject(key, DeliveryServiceSSLKeysBucket, cluster); err != nil {
+ return errors.New("deleting SSL keys: " + err.Error())
+ }
+ return nil
+ })
+ return err
+}
+
// GetURLSigConfigFileName returns the filename of the Apache Traffic Server URLSig config file
// TODO move to ats config directory/file
func GetURLSigConfigFileName(ds tc.DeliveryServiceName) string {
@@ -260,7 +273,8 @@ func GetCDNSSLKeysObj(tx *sql.Tx, authOpts *riak.AuthOptions, riakPort *uint, cd
// get the deliveryservice ssl keys by xmlID and version
query := `cdn:` + cdnName
filterQuery := `_yz_rk:*latest`
- searchDocs, err := Search(cluster, SSLKeysIndex, query, filterQuery, CDNSSLKeysLimit)
+ fields := []string{"deliveryservice", "hostname", "certificate.crt", "certificate.key"}
+ searchDocs, err := Search(cluster, SSLKeysIndex, query, filterQuery, CDNSSLKeysLimit, fields)
if err != nil {
return errors.New("riak search error: " + err.Error())
}
@@ -297,3 +311,41 @@ func SearchDocsToCDNSSLKeys(docs []*riak.SearchDoc) []tc.CDNSSLKey {
}
return keys
}
+
+// GetCDNSSLKeysDSNames returns the delivery service names (xml_id) of every delivery service on the given cdn with SSL keys in Riak, and the keys currently in Riak.
+// Returns map[tc.DeliveryServiceName][]key
+func GetCDNSSLKeysDSNames(tx *sql.Tx, authOpts *riak.AuthOptions, riakPort *uint, cdn tc.CDNName) (map[tc.DeliveryServiceName][]string, error) {
+ dsVersions := map[tc.DeliveryServiceName][]string{}
+ err := WithCluster(tx, authOpts, riakPort, func(cluster StorageCluster) error {
+ // get the deliveryservice ssl keys by xmlID and version
+ query := `cdn:` + string(cdn)
+ filterQuery := ""
+ fields := []string{"_yz_rk", "deliveryservice"} // '_yz_rk' is the magic Riak field that populates the key. Without this, doc.Key would be empty.
+ searchDocs, err := Search(cluster, SSLKeysIndex, query, filterQuery, CDNSSLKeysLimit, fields)
+ if err != nil {
+ return errors.New("riak search error: " + err.Error())
+ }
+ if len(searchDocs) == 0 {
+ return nil // no error, and leave keys empty
+ }
+
+ for _, doc := range searchDocs {
+ dses := doc.Fields["deliveryservice"]
+ if len(dses) == 0 {
+ log.Errorln("Riak had a CDN '" + string(cdn) + "' key with no delivery service '" + doc.Key + "' - ignoring!")
+ continue
+ }
+ if len(dses) > 1 {
+ log.Errorf("Riak had a CDN '"+string(cdn)+"' key with multiple delivery services '"+doc.Key+"' deliveryservices '%+v' - ignoring all but the first!\n", dses)
+ }
+ ds := tc.DeliveryServiceName(dses[0])
+
+ dsVersions[ds] = append(dsVersions[ds], doc.Key)
+ }
+ return nil
+ })
+ if err != nil {
+ return nil, errors.New("with cluster error: " + err.Error())
+ }
+ return dsVersions, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go b/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go
index 9b1287e..e0b5bed 100644
--- a/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go
+++ b/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go
@@ -196,7 +196,6 @@ func FetchObjectValues(key string, bucket string, cluster StorageCluster) ([]*ri
return fvc.Response.Values, nil
}
-// saves an object to riak storage
func SaveObject(obj *riak.Object, bucket string, cluster StorageCluster) error {
if cluster == nil {
return errors.New("ERROR: No valid cluster on which to execute a command")
@@ -357,13 +356,20 @@ func WithCluster(tx *sql.Tx, authOpts *riak.AuthOptions, riakPort *uint, f func(
}
// Search searches Riak for the given query. Returns nil and a nil error if no object was found.
-func Search(cluster StorageCluster, index string, query string, filterQuery string, numRows int) ([]*riak.SearchDoc, error) {
- iCmd, err := riak.NewSearchCommandBuilder().
+// If fields is empty, all fields will be returned.
+func Search(cluster StorageCluster, index string, query string, filterQuery string, numRows int, fields []string) ([]*riak.SearchDoc, error) {
+ riakCmd := riak.NewSearchCommandBuilder().
WithIndexName(index).
WithQuery(query).
- WithFilterQuery(filterQuery).
- WithNumRows(uint32(numRows)).
- Build()
+ WithNumRows(uint32(numRows))
+ if len(filterQuery) > 0 {
+ riakCmd = riakCmd.WithFilterQuery(filterQuery)
+ }
+ if len(fields) > 0 {
+ riakCmd = riakCmd.WithReturnFields(fields...)
+ }
+ iCmd, err := riakCmd.Build()
+
if err != nil {
return nil, errors.New("building Riak command: " + err.Error())
}