You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2018/03/07 20:16:08 UTC

[GitHub] dangogh closed pull request #1967: Add Tenancy to URI Signing endpoints and refactor riak code into a riaksvc package.

dangogh closed pull request #1967: Add Tenancy to URI Signing endpoints and refactor riak code into a riaksvc package.
URL: https://github.com/apache/incubator-trafficcontrol/pull/1967
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/traffic_ops/traffic_ops_golang/config.go b/traffic_ops/traffic_ops_golang/config.go
index 8b47610f64..0d58dabb7c 100644
--- a/traffic_ops/traffic_ops_golang/config.go
+++ b/traffic_ops/traffic_ops_golang/config.go
@@ -25,9 +25,8 @@ import (
 	"io/ioutil"
 	"net/url"
 
-	"crypto/tls"
-
 	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/riaksvc"
 	"github.com/basho/riak-go-client"
 )
 
@@ -136,20 +135,10 @@ func LoadConfig(cdnConfPath string, dbConfPath string, riakConfPath string) (Con
 	}
 
 	if riakConfPath != "" {
-		riakConfBytes, err := ioutil.ReadFile(riakConfPath)
-		if err != nil {
-			cfg.RiakAuthOptions = nil
-			return cfg, fmt.Errorf("reading riak conf '%v': %v", riakConfPath, err)
-		}
-		riakconf, err := getRiakAuthOptions(string(riakConfBytes))
+		cfg.RiakEnabled, cfg.RiakAuthOptions, err = riaksvc.GetRiakConfig(riakConfPath)
 		if err != nil {
-			cfg.RiakAuthOptions = nil
-			return cfg, fmt.Errorf("parsing riak conf '%v': %v", riakConfBytes, err)
+			return Config{}, fmt.Errorf("parsing config '%s': %v", riakConfPath, err)
 		}
-		cfg.RiakAuthOptions = riakconf
-		cfg.RiakEnabled = true
-	} else {
-		cfg.RiakEnabled = false
 	}
 
 	return cfg, err
@@ -173,13 +162,6 @@ func (c Config) GetKeyPath() string {
 	return ""
 }
 
-func getRiakAuthOptions(s string) (*riak.AuthOptions, error) {
-	rconf := &riak.AuthOptions{}
-	rconf.TlsConfig = &tls.Config{}
-	err := json.Unmarshal([]byte(s), &rconf)
-	return rconf, err
-}
-
 const (
 	// MojoliciousConcurrentConnectionsDefault  ...
 	MojoliciousConcurrentConnectionsDefault = 12
diff --git a/traffic_ops/traffic_ops_golang/deliveryservices_keys.go b/traffic_ops/traffic_ops_golang/deliveryservices_keys.go
index ee2b870fa0..11a6de02b5 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservices_keys.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservices_keys.go
@@ -35,6 +35,7 @@ import (
 	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/riaksvc"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
 	"github.com/basho/riak-go-client"
 	"github.com/jmoiron/sqlx"
@@ -42,6 +43,9 @@ import (
 
 // Delivery Services: SSL Keys.
 
+// SSLKeysBucket ...
+const SSLKeysBucket = "ssl"
+
 // returns the cdn_id found by domainname.
 func getCDNIDByDomainname(domainName string, db *sqlx.DB) (sql.NullInt64, error) {
 	cdnQuery := `SELECT id from cdn WHERE domain_name = $1`
@@ -98,7 +102,7 @@ func getXMLID(cdnID sql.NullInt64, hostRegex string, db *sqlx.DB) (sql.NullStrin
 func getDeliveryServiceSSLKeysByXMLID(xmlID string, version string, db *sqlx.DB, cfg Config) ([]byte, error) {
 	var respBytes []byte
 	// create and start a cluster
-	cluster, err := getRiakCluster(db, cfg)
+	cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
 	if err != nil {
 		return nil, err
 	}
@@ -118,7 +122,7 @@ func getDeliveryServiceSSLKeysByXMLID(xmlID string, version string, db *sqlx.DB,
 	}
 
 	// get the deliveryservice ssl keys by xmlID and version
-	ro, err := fetchObjectValues(xmlID, SSLKeysBucket, cluster)
+	ro, err := riaksvc.FetchObjectValues(xmlID, SSLKeysBucket, cluster)
 	if err != nil {
 		return nil, err
 	}
@@ -297,7 +301,7 @@ func addDeliveryServiceSSLKeysHandler(db *sqlx.DB, cfg Config) http.HandlerFunc
 		}
 
 		// create and start a cluster
-		cluster, err := getRiakCluster(db, cfg)
+		cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
 		if err != nil {
 			handleErr(http.StatusInternalServerError, err)
 			return
@@ -321,7 +325,7 @@ func addDeliveryServiceSSLKeysHandler(db *sqlx.DB, cfg Config) http.HandlerFunc
 			Value:           []byte(keysJSON),
 		}
 
-		err = saveObject(obj, SSLKeysBucket, cluster)
+		err = riaksvc.SaveObject(obj, SSLKeysBucket, cluster)
 		if err != nil {
 			log.Errorf("%v\n", err)
 			handleErr(http.StatusInternalServerError, err)
@@ -490,207 +494,3 @@ func getDeliveryServiceSSLKeysByXMLIDHandler(db *sqlx.DB, cfg Config) http.Handl
 		fmt.Fprintf(w, "%s", respBytes)
 	}
 }
-
-// Delivery Services: URI Sign Keys.
-
-// Http POST or PUT handler used to store urisigning keys to a delivery service.
-func saveDeliveryServiceURIKeysHandler(db *sqlx.DB, cfg Config) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		handleErr := tc.GetHandleErrorsFunc(w, r)
-
-		defer r.Body.Close()
-
-		if cfg.RiakEnabled == false {
-			handleErr(http.StatusServiceUnavailable, fmt.Errorf("The RIAK service is unavailable"))
-			return
-		}
-
-		ctx := r.Context()
-		pathParams, err := api.GetPathParams(ctx)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-
-		xmlID := pathParams["xmlID"]
-		data, err := ioutil.ReadAll(r.Body)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-
-		// validate that the received data is a valid jwk keyset
-		var keySet map[string]URISignerKeyset
-		if err := json.Unmarshal(data, &keySet); err != nil {
-			log.Errorf("%v\n", err)
-			handleErr(http.StatusBadRequest, err)
-			return
-		}
-		if err := validateURIKeyset(keySet); err != nil {
-			log.Errorf("%v\n", err)
-			handleErr(http.StatusBadRequest, err)
-			return
-		}
-
-		// create and start a cluster
-		cluster, err := getRiakCluster(db, cfg)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-		if err = cluster.Start(); err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-		defer func() {
-			if err := cluster.Stop(); err != nil {
-				log.Errorf("%v\n", err)
-			}
-		}()
-
-		// create a storage object and store the data
-		obj := &riak.Object{
-			ContentType:     "text/json",
-			Charset:         "utf-8",
-			ContentEncoding: "utf-8",
-			Key:             xmlID,
-			Value:           []byte(data),
-		}
-
-		err = saveObject(obj, CDNURIKeysBucket, cluster)
-		if err != nil {
-			log.Errorf("%v\n", err)
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-
-		w.Header().Set("Content-Type", "application/json")
-		fmt.Fprintf(w, "%s", data)
-	}
-}
-
-// endpoint handler for fetching uri signing keys from riak
-func getURIsignkeysHandler(db *sqlx.DB, cfg Config) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		handleErr := tc.GetHandleErrorsFunc(w, r)
-
-		if cfg.RiakEnabled == false {
-			handleErr(http.StatusServiceUnavailable, fmt.Errorf("The RIAK service is unavailable"))
-			return
-		}
-
-		ctx := r.Context()
-		pathParams, err := api.GetPathParams(ctx)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-
-		xmlID := pathParams["xmlID"]
-
-		// create and start a cluster
-		cluster, err := getRiakCluster(db, cfg)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-		if err = cluster.Start(); err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-		defer func() {
-			if err := cluster.Stop(); err != nil {
-				log.Errorf("%v\n", err)
-			}
-		}()
-
-		ro, err := fetchObjectValues(xmlID, CDNURIKeysBucket, cluster)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-
-		var respBytes []byte
-
-		if ro == nil {
-			var empty URISignerKeyset
-			respBytes, err = json.Marshal(empty)
-			if err != nil {
-				log.Errorf("failed to marshal an empty response: %s\n", err)
-				w.WriteHeader(http.StatusInternalServerError)
-				fmt.Fprintf(w, http.StatusText(http.StatusInternalServerError))
-				return
-			}
-		} else {
-			respBytes = ro[0].Value
-		}
-		w.Header().Set("Content-Type", "application/json")
-		fmt.Fprintf(w, "%s", respBytes)
-	}
-}
-
-// Http DELETE handler used to remove urisigning keys assigned to a delivery service.
-func removeDeliveryServiceURIKeysHandler(db *sqlx.DB, cfg Config) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		handleErr := tc.GetHandleErrorsFunc(w, r)
-
-		if cfg.RiakEnabled == false {
-			handleErr(http.StatusServiceUnavailable, fmt.Errorf("The RIAK service is unavailable"))
-			return
-		}
-
-		ctx := r.Context()
-		pathParams, err := api.GetPathParams(ctx)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-
-		xmlID := pathParams["xmlID"]
-
-		// create and start a cluster
-		cluster, err := getRiakCluster(db, cfg)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-		if err = cluster.Start(); err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-		defer func() {
-			if err := cluster.Stop(); err != nil {
-				log.Errorf("%v\n", err)
-			}
-		}()
-
-		ro, err := fetchObjectValues(xmlID, CDNURIKeysBucket, cluster)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-
-		// fetch the object and delete it if it exists.
-		var alert tc.Alerts
-
-		if ro == nil || ro[0].Value == nil {
-			alert = tc.CreateAlerts(tc.InfoLevel, "not deleted, no object found to delete")
-		} else if err := deleteObject(xmlID, CDNURIKeysBucket, cluster); err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		} else { // object successfully deleted
-			alert = tc.CreateAlerts(tc.SuccessLevel, "object deleted")
-		}
-
-		// send response
-		respBytes, err := json.Marshal(alert)
-		if err != nil {
-			log.Errorf("failed to marshal an alert response: %s\n", err)
-			w.WriteHeader(http.StatusInternalServerError)
-			fmt.Fprintf(w, http.StatusText(http.StatusInternalServerError))
-			return
-		}
-		w.Header().Set("Content-Type", "application/json")
-		fmt.Fprintf(w, "%s", respBytes)
-	}
-}
diff --git a/traffic_ops/traffic_ops_golang/riak.go b/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go
similarity index 68%
rename from traffic_ops/traffic_ops_golang/riak.go
rename to traffic_ops/traffic_ops_golang/riaksvc/riak_services.go
index cfd33f3869..978c39f9d5 100644
--- a/traffic_ops/traffic_ops_golang/riak.go
+++ b/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go
@@ -1,4 +1,4 @@
-package main
+package riaksvc
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -20,32 +20,29 @@ package main
  */
 
 import (
+	"crypto/tls"
+	"encoding/json"
 	"errors"
 	"fmt"
-	"strings"
+	"io/ioutil"
 	"time"
 
 	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
 	"github.com/basho/riak-go-client"
 	"github.com/jmoiron/sqlx"
-	"github.com/lestrrat/go-jwx/jwk"
 )
 
 // RiakPort is the port RIAK is listening on.
 const RiakPort = 8087
 
-// CDNURIKeysBucket is the namespace or bucket used for CDN URI signing keys.
-const CDNURIKeysBucket = "cdn_uri_sig_keys"
-
-// SSLKeysBucket ...
-const SSLKeysBucket = "ssl"
-
 // 5 second timeout
 const timeOut = time.Second * 5
 
 // MaxCommandExecutionAttempts ...
 const MaxCommandExecutionAttempts = 5
 
+type AuthOptions riak.AuthOptions
+
 // StorageCluster ...
 type StorageCluster interface {
 	Start() error
@@ -73,14 +70,24 @@ func (ri RiakStorageCluster) Execute(command riak.Command) error {
 	return ri.Cluster.Execute(command)
 }
 
-// URISignerKeyset is the container for the CDN URI signing keys
-type URISignerKeyset struct {
-	RenewalKid *string               `json:"renewal_kid"`
-	Keys       []jwk.EssentialHeader `json:"keys"`
+func GetRiakConfig(riakConfigFile string) (bool, *riak.AuthOptions, error) {
+	riakConfBytes, err := ioutil.ReadFile(riakConfigFile)
+	if err != nil {
+		return false, nil, fmt.Errorf("reading riak conf '%v': %v", riakConfigFile, err)
+	}
+
+	rconf := &riak.AuthOptions{}
+	rconf.TlsConfig = &tls.Config{}
+	err = json.Unmarshal([]byte(riakConfBytes), &rconf)
+	if err != nil {
+		return false, nil, fmt.Errorf("Unmarshalling riak conf '%v': %v", riakConfigFile, err)
+	}
+
+	return true, rconf, nil
 }
 
 // deletes an object from riak storage
-func deleteObject(key string, bucket string, cluster StorageCluster) error {
+func DeleteObject(key string, bucket string, cluster StorageCluster) error {
 	if cluster == nil {
 		return errors.New("ERROR: No valid cluster on which to execute a command")
 	}
@@ -105,7 +112,7 @@ func deleteObject(key string, bucket string, cluster StorageCluster) error {
 }
 
 // fetch an object from riak storage
-func fetchObjectValues(key string, bucket string, cluster StorageCluster) ([]*riak.Object, error) {
+func FetchObjectValues(key string, bucket string, cluster StorageCluster) ([]*riak.Object, error) {
 	if cluster == nil {
 		return nil, errors.New("ERROR: No valid cluster on which to execute a command")
 	}
@@ -132,7 +139,7 @@ func fetchObjectValues(key string, bucket string, cluster StorageCluster) ([]*ri
 }
 
 // saves an object to riak storage
-func saveObject(obj *riak.Object, bucket string, cluster StorageCluster) error {
+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")
 	}
@@ -157,15 +164,15 @@ func saveObject(obj *riak.Object, bucket string, cluster StorageCluster) error {
 }
 
 // returns a riak cluster of online riak nodes.
-func getRiakCluster(db *sqlx.DB, cfg Config) (StorageCluster, error) {
+func GetRiakCluster(db *sqlx.DB, authOptions *riak.AuthOptions) (StorageCluster, error) {
 	riakServerQuery := `
-		SELECT s.host_name, s.domain_name FROM server s 
-		INNER JOIN type t on s.type = t.id 
-		INNER JOIN status st on s.status = st.id 
+		SELECT s.host_name, s.domain_name FROM server s
+		INNER JOIN type t on s.type = t.id
+		INNER JOIN status st on s.status = st.id
 		WHERE t.name = 'RIAK' AND st.name = 'ONLINE'
 		`
 
-	if cfg.RiakAuthOptions == nil {
+	if authOptions == nil {
 		return nil, errors.New("ERROR: no riak auth information from riak.conf, cannot authenticate to any riak servers")
 	}
 
@@ -185,7 +192,7 @@ func getRiakCluster(db *sqlx.DB, cfg Config) (StorageCluster, error) {
 		addr := fmt.Sprintf("%s.%s:%d", s.HostName, s.DomainName, RiakPort)
 		nodeOpts := &riak.NodeOptions{
 			RemoteAddress: addr,
-			AuthOptions:   cfg.RiakAuthOptions,
+			AuthOptions:   authOptions,
 		}
 		nodeOpts.AuthOptions.TlsConfig.ServerName = fmt.Sprintf("%s.%s", s.HostName, s.DomainName)
 		n, err := riak.NewNode(nodeOpts)
@@ -208,50 +215,3 @@ func getRiakCluster(db *sqlx.DB, cfg Config) (StorageCluster, error) {
 
 	return RiakStorageCluster{Cluster: cluster}, err
 }
-
-// validates URISigingKeyset json.
-func validateURIKeyset(msg map[string]URISignerKeyset) error {
-	var renewalKidFound int
-	var renewalKidMatched = false
-
-	for key, value := range msg {
-		issuer := key
-		renewalKid := value.RenewalKid
-		if issuer == "" {
-			return errors.New("JSON Keyset has no issuer")
-		}
-
-		if renewalKid != nil {
-			renewalKidFound++
-		}
-
-		for _, skey := range value.Keys {
-			if skey.Algorithm == "" {
-				return errors.New("A Key has no algorithm, alg, specified")
-			}
-			if skey.KeyID == "" {
-				return errors.New("A Key has no key id, kid, specified")
-			}
-			if renewalKid != nil && strings.Compare(*renewalKid, skey.KeyID) == 0 {
-				renewalKidMatched = true
-			}
-		}
-	}
-
-	// should only have one renewal_kid
-	switch renewalKidFound {
-	case 0:
-		return errors.New("No renewal_kid was found in any keyset")
-	case 1: // okay, this is what we want
-		break
-	default:
-		return errors.New("More than one renewal_kid was found in the keysets")
-	}
-
-	// the renewal_kid should match the kid of one key
-	if !renewalKidMatched {
-		return errors.New("No key was found with a kid that matches the renewal kid")
-	}
-
-	return nil
-}
diff --git a/traffic_ops/traffic_ops_golang/riaksvc/riak_services_test.go b/traffic_ops/traffic_ops_golang/riaksvc/riak_services_test.go
new file mode 100644
index 0000000000..d819333f76
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/riaksvc/riak_services_test.go
@@ -0,0 +1,174 @@
+package riaksvc
+
+/*
+ * 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 (
+	"crypto/tls"
+	"errors"
+	"testing"
+
+	"github.com/basho/riak-go-client"
+	"github.com/jmoiron/sqlx"
+	"gopkg.in/DATA-DOG/go-sqlmock.v1"
+)
+
+type MockStorageCluster struct {
+	Bucket  string
+	Key     string
+	Running bool
+}
+
+func (mc MockStorageCluster) Stop() error {
+	if mc.Running == true {
+		mc.Running = false
+	} else {
+		return errors.New("the cluster is not started")
+	}
+	return nil
+}
+
+func (mc MockStorageCluster) Start() error {
+	if mc.Running == false {
+		mc.Running = true
+	} else {
+		return errors.New("the cluster is already started")
+	}
+	return nil
+}
+
+func (mc MockStorageCluster) Execute(command riak.Command) error {
+	return nil
+}
+
+func TestFetchObjectValues(t *testing.T) {
+	cluster := &MockStorageCluster{
+		Running: true,
+	}
+
+	_, err := FetchObjectValues("myobject", "bucket", cluster)
+	if err != nil {
+		t.Error("expected nil error got ", err)
+	}
+
+	_, err = FetchObjectValues("", "bucket", cluster)
+	if err == nil {
+		t.Error("expected an error because key is empty but got no error")
+	}
+
+	_, err = FetchObjectValues("myobject", "", cluster)
+	if err == nil {
+		t.Error("expected an error because the bucket name is empty but got no error")
+	}
+
+	_, err = FetchObjectValues("myobject", "mybucket", nil)
+	if err == nil {
+		t.Error("expected an error because the cluster is nil but got no error")
+	}
+}
+
+func TestSaveObject(t *testing.T) {
+	cluster := &MockStorageCluster{
+		Running: true,
+	}
+
+	obj := riak.Object{}
+
+	err := SaveObject(&obj, "bucket", cluster)
+	if err != nil {
+		t.Error("expected nil error got ", err)
+	}
+
+	err = SaveObject(&obj, "", cluster)
+	if err == nil {
+		t.Error("expected an error due to empty bucket name but got a nil error")
+	}
+
+	err = SaveObject(nil, "bucket", cluster)
+	if err == nil {
+		t.Error("expected an error because the obj is nil but go no error", err)
+	}
+
+	err = SaveObject(&obj, "bucket", nil)
+	if err == nil {
+		t.Error("expected an error because the cluster is nil but go no error", err)
+	}
+}
+
+func TestDeleteObject(t *testing.T) {
+	cluster := &MockStorageCluster{
+		Running: true,
+	}
+
+	err := DeleteObject("myobject", "bucket", cluster)
+	if err != nil {
+		t.Error("expected nil error got ", err)
+	}
+
+	err = DeleteObject("", "bucket", cluster)
+	if err == nil {
+		t.Error("expected an empty key error but got nil error", err)
+	}
+
+	err = DeleteObject("myobject", "", cluster)
+	if err == nil {
+		t.Error("expected an empty bucket error but got nil error", err)
+	}
+
+	err = DeleteObject("myobject", "bucket", nil)
+	if err == nil {
+		t.Error("expected an nil cluster error but got nil error", err)
+	}
+}
+
+func TestGetRiakCluster(t *testing.T) {
+	mockDB, mock, err := sqlmock.New()
+	if err != nil {
+		t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
+	}
+	defer mockDB.Close()
+
+	db := sqlx.NewDb(mockDB, "sqlmock")
+	defer db.Close()
+
+	rows1 := sqlmock.NewRows([]string{"s.host_name", "s.domain_name"})
+	rows1.AddRow("www", "devnull.com")
+	mock.ExpectQuery("SELECT").WillReturnRows(rows1)
+
+	if _, err := GetRiakCluster(db, nil); err == nil {
+		t.Errorf("expected an error due to nil RiakAuthoptions in the config but, go no error.")
+	}
+
+	authOptions := riak.AuthOptions{
+		User:      "riakuser",
+		Password:  "password",
+		TlsConfig: &tls.Config{},
+	}
+
+	if _, err := GetRiakCluster(db, &authOptions); err != nil {
+		t.Errorf("expected no errors, actual: %s.", err)
+	}
+
+	rows2 := sqlmock.NewRows([]string{"s.host_name", "s.domain_name"})
+	mock.ExpectQuery("SELECT").WillReturnRows(rows2)
+
+	if _, err := GetRiakCluster(db, &authOptions); err == nil {
+		t.Errorf("expected an error due to no available riak servers.")
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/urisigning.go b/traffic_ops/traffic_ops_golang/urisigning.go
new file mode 100644
index 0000000000..2fddee8195
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/urisigning.go
@@ -0,0 +1,364 @@
+package main
+
+/*
+ * 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 (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/riaksvc"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
+	"github.com/basho/riak-go-client"
+	"github.com/jmoiron/sqlx"
+	"github.com/lestrrat/go-jwx/jwk"
+)
+
+// CDNURIKeysBucket is the namespace or bucket used for CDN URI signing keys.
+const CDNURIKeysBucket = "cdn_uri_sig_keys"
+
+// URISignerKeyset is the container for the CDN URI signing keys
+type URISignerKeyset struct {
+	RenewalKid *string               `json:"renewal_kid"`
+	Keys       []jwk.EssentialHeader `json:"keys"`
+}
+
+// endpoint handler for fetching uri signing keys from riak
+func getURIsignkeysHandler(db *sqlx.DB, cfg Config) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		handleErr := tc.GetHandleErrorsFunc(w, r)
+
+		if cfg.RiakEnabled == false {
+			handleErr(http.StatusServiceUnavailable, fmt.Errorf("The RIAK service is unavailable"))
+			return
+		}
+
+		ctx := r.Context()
+		pathParams, err := api.GetPathParams(ctx)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		user, err := auth.GetCurrentUser(ctx)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		xmlID := pathParams["xmlID"]
+
+		// check user tenancy access to this resource.
+		hasAccess, err, apiStatus := tenant.HasTenant(*user, xmlID, db)
+		if !hasAccess {
+			switch apiStatus {
+			case tc.SystemError:
+				handleErr(http.StatusInternalServerError, err)
+				return
+			case tc.DataMissingError:
+				handleErr(http.StatusBadRequest, err)
+				return
+			case tc.ForbiddenError:
+				handleErr(http.StatusForbidden, err)
+				return
+			}
+		}
+
+		// create and start a cluster
+		cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+		if err = cluster.Start(); err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+		defer func() {
+			if err := cluster.Stop(); err != nil {
+				log.Errorf("%v\n", err)
+			}
+		}()
+
+		ro, err := riaksvc.FetchObjectValues(xmlID, CDNURIKeysBucket, cluster)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		var respBytes []byte
+
+		if ro == nil {
+			var empty URISignerKeyset
+			respBytes, err = json.Marshal(empty)
+			if err != nil {
+				log.Errorf("failed to marshal an empty response: %s\n", err)
+				w.WriteHeader(http.StatusInternalServerError)
+				fmt.Fprintf(w, http.StatusText(http.StatusInternalServerError))
+				return
+			}
+		} else {
+			respBytes = ro[0].Value
+		}
+		w.Header().Set("Content-Type", "application/json")
+		fmt.Fprintf(w, "%s", respBytes)
+	}
+}
+
+// Http DELETE handler used to remove urisigning keys assigned to a delivery service.
+func removeDeliveryServiceURIKeysHandler(db *sqlx.DB, cfg Config) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		handleErr := tc.GetHandleErrorsFunc(w, r)
+
+		if cfg.RiakEnabled == false {
+			handleErr(http.StatusServiceUnavailable, fmt.Errorf("The RIAK service is unavailable"))
+			return
+		}
+
+		ctx := r.Context()
+		pathParams, err := api.GetPathParams(ctx)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		user, err := auth.GetCurrentUser(ctx)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		xmlID := pathParams["xmlID"]
+
+		// check user tenancy access to this resource.
+		hasAccess, err, apiStatus := tenant.HasTenant(*user, xmlID, db)
+		if !hasAccess {
+			switch apiStatus {
+			case tc.SystemError:
+				handleErr(http.StatusInternalServerError, err)
+				return
+			case tc.DataMissingError:
+				handleErr(http.StatusBadRequest, err)
+				return
+			case tc.ForbiddenError:
+				handleErr(http.StatusForbidden, err)
+				return
+			}
+		}
+
+		// create and start a cluster
+		cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+		if err = cluster.Start(); err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+		defer func() {
+			if err := cluster.Stop(); err != nil {
+				log.Errorf("%v\n", err)
+			}
+		}()
+
+		ro, err := riaksvc.FetchObjectValues(xmlID, CDNURIKeysBucket, cluster)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		// fetch the object and delete it if it exists.
+		var alert tc.Alerts
+
+		if ro == nil || ro[0].Value == nil {
+			alert = tc.CreateAlerts(tc.InfoLevel, "not deleted, no object found to delete")
+		} else if err := riaksvc.DeleteObject(xmlID, CDNURIKeysBucket, cluster); err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		} else { // object successfully deleted
+			alert = tc.CreateAlerts(tc.SuccessLevel, "object deleted")
+		}
+
+		// send response
+		respBytes, err := json.Marshal(alert)
+		if err != nil {
+			log.Errorf("failed to marshal an alert response: %s\n", err)
+			w.WriteHeader(http.StatusInternalServerError)
+			fmt.Fprintf(w, http.StatusText(http.StatusInternalServerError))
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		fmt.Fprintf(w, "%s", respBytes)
+	}
+}
+
+// Http POST or PUT handler used to store urisigning keys to a delivery service.
+func saveDeliveryServiceURIKeysHandler(db *sqlx.DB, cfg Config) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		handleErr := tc.GetHandleErrorsFunc(w, r)
+
+		defer r.Body.Close()
+
+		if cfg.RiakEnabled == false {
+			handleErr(http.StatusServiceUnavailable, fmt.Errorf("The RIAK service is unavailable"))
+			return
+		}
+
+		ctx := r.Context()
+		pathParams, err := api.GetPathParams(ctx)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		user, err := auth.GetCurrentUser(ctx)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		xmlID := pathParams["xmlID"]
+
+		// check user tenancy access to this resource.
+		hasAccess, err, apiStatus := tenant.HasTenant(*user, xmlID, db)
+		if !hasAccess {
+			switch apiStatus {
+			case tc.SystemError:
+				handleErr(http.StatusInternalServerError, err)
+				return
+			case tc.DataMissingError:
+				handleErr(http.StatusBadRequest, err)
+				return
+			case tc.ForbiddenError:
+				handleErr(http.StatusForbidden, err)
+				return
+			}
+		}
+
+		data, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		// validate that the received data is a valid jwk keyset
+		var keySet map[string]URISignerKeyset
+		if err := json.Unmarshal(data, &keySet); err != nil {
+			log.Errorf("%v\n", err)
+			handleErr(http.StatusBadRequest, err)
+			return
+		}
+		if err := validateURIKeyset(keySet); err != nil {
+			log.Errorf("%v\n", err)
+			handleErr(http.StatusBadRequest, err)
+			return
+		}
+
+		// create and start a cluster
+		cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
+		if err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+		if err = cluster.Start(); err != nil {
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+		defer func() {
+			if err := cluster.Stop(); err != nil {
+				log.Errorf("%v\n", err)
+			}
+		}()
+
+		// create a storage object and store the data
+		obj := &riak.Object{
+			ContentType:     "text/json",
+			Charset:         "utf-8",
+			ContentEncoding: "utf-8",
+			Key:             xmlID,
+			Value:           []byte(data),
+		}
+
+		err = riaksvc.SaveObject(obj, CDNURIKeysBucket, cluster)
+		if err != nil {
+			log.Errorf("%v\n", err)
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
+
+		w.Header().Set("Content-Type", "application/json")
+		fmt.Fprintf(w, "%s", data)
+	}
+}
+
+// validates URISigingKeyset json.
+func validateURIKeyset(msg map[string]URISignerKeyset) error {
+	var renewalKidFound int
+	var renewalKidMatched = false
+
+	for key, value := range msg {
+		issuer := key
+		renewalKid := value.RenewalKid
+		if issuer == "" {
+			return errors.New("JSON Keyset has no issuer")
+		}
+
+		if renewalKid != nil {
+			renewalKidFound++
+		}
+
+		for _, skey := range value.Keys {
+			if skey.Algorithm == "" {
+				return errors.New("A Key has no algorithm, alg, specified")
+			}
+			if skey.KeyID == "" {
+				return errors.New("A Key has no key id, kid, specified")
+			}
+			if renewalKid != nil && strings.Compare(*renewalKid, skey.KeyID) == 0 {
+				renewalKidMatched = true
+			}
+		}
+	}
+
+	// should only have one renewal_kid
+	switch renewalKidFound {
+	case 0:
+		return errors.New("No renewal_kid was found in any keyset")
+	case 1: // okay, this is what we want
+		break
+	default:
+		return errors.New("More than one renewal_kid was found in the keysets")
+	}
+
+	// the renewal_kid should match the kid of one key
+	if !renewalKidMatched {
+		return errors.New("No key was found with a kid that matches the renewal kid")
+	}
+
+	return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/riak_test.go b/traffic_ops/traffic_ops_golang/urisigning_test.go
similarity index 53%
rename from traffic_ops/traffic_ops_golang/riak_test.go
rename to traffic_ops/traffic_ops_golang/urisigning_test.go
index 838dbe4a55..98dcfef573 100644
--- a/traffic_ops/traffic_ops_golang/riak_test.go
+++ b/traffic_ops/traffic_ops_golang/urisigning_test.go
@@ -20,14 +20,8 @@ package main
  */
 
 import (
-	"crypto/tls"
 	"encoding/json"
-	"errors"
 	"testing"
-
-	"github.com/basho/riak-go-client"
-	"github.com/jmoiron/sqlx"
-	"gopkg.in/DATA-DOG/go-sqlmock.v1"
 )
 
 const (
@@ -132,114 +126,6 @@ const (
 `
 )
 
-type MockStorageCluster struct {
-	Bucket  string
-	Key     string
-	Running bool
-}
-
-func (mc MockStorageCluster) Stop() error {
-	if mc.Running == true {
-		mc.Running = false
-	} else {
-		return errors.New("the cluster is not started")
-	}
-	return nil
-}
-
-func (mc MockStorageCluster) Start() error {
-	if mc.Running == false {
-		mc.Running = true
-	} else {
-		return errors.New("the cluster is already started")
-	}
-	return nil
-}
-
-func (mc MockStorageCluster) Execute(command riak.Command) error {
-	return nil
-}
-
-func TestFetchObjectValues(t *testing.T) {
-	cluster := &MockStorageCluster{
-		Running: true,
-	}
-
-	_, err := fetchObjectValues("myobject", "bucket", cluster)
-	if err != nil {
-		t.Error("expected nil error got ", err)
-	}
-
-	_, err = fetchObjectValues("", "bucket", cluster)
-	if err == nil {
-		t.Error("expected an error because key is empty but got no error")
-	}
-
-	_, err = fetchObjectValues("myobject", "", cluster)
-	if err == nil {
-		t.Error("expected an error because the bucket name is empty but got no error")
-	}
-
-	_, err = fetchObjectValues("myobject", "mybucket", nil)
-	if err == nil {
-		t.Error("expected an error because the cluster is nil but got no error")
-	}
-}
-
-func TestSaveObject(t *testing.T) {
-	cluster := &MockStorageCluster{
-		Running: true,
-	}
-
-	obj := riak.Object{}
-
-	err := saveObject(&obj, "bucket", cluster)
-	if err != nil {
-		t.Error("expected nil error got ", err)
-	}
-
-	err = saveObject(&obj, "", cluster)
-	if err == nil {
-		t.Error("expected an error due to empty bucket name but got a nil error")
-	}
-
-	err = saveObject(nil, "bucket", cluster)
-	if err == nil {
-		t.Error("expected an error because the obj is nil but go no error", err)
-	}
-
-	err = saveObject(&obj, "bucket", nil)
-	if err == nil {
-		t.Error("expected an error because the cluster is nil but go no error", err)
-	}
-}
-
-func TestDeleteObject(t *testing.T) {
-	cluster := &MockStorageCluster{
-		Running: true,
-	}
-
-	err := deleteObject("myobject", "bucket", cluster)
-	if err != nil {
-		t.Error("expected nil error got ", err)
-	}
-
-	err = deleteObject("", "bucket", cluster)
-	if err == nil {
-		t.Error("expected an empty key error but got nil error", err)
-	}
-
-	err = deleteObject("myobject", "", cluster)
-	if err == nil {
-		t.Error("expected an empty bucket error but got nil error", err)
-	}
-
-	err = deleteObject("myobject", "bucket", nil)
-	if err == nil {
-		t.Error("expected an nil cluster error but got nil error", err)
-	}
-}
-
 func TestValidateURIKeyset(t *testing.T) {
 	var keyset map[string]URISignerKeyset
 
@@ -278,41 +164,3 @@ func TestValidateURIKeyset(t *testing.T) {
 		t.Errorf("validateURIKeyset(): expected an error")
 	}
 }
-
-func TestGetRiakCluster(t *testing.T) {
-	var cfg Config
-	mockDB, mock, err := sqlmock.New()
-	if err != nil {
-		t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
-	}
-	defer mockDB.Close()
-
-	db := sqlx.NewDb(mockDB, "sqlmock")
-	defer db.Close()
-
-	rows1 := sqlmock.NewRows([]string{"s.host_name", "s.domain_name"})
-	rows1.AddRow("www", "devnull.com")
-	mock.ExpectQuery("SELECT").WillReturnRows(rows1)
-
-	cfg.RiakAuthOptions = nil
-	if _, err := getRiakCluster(db, cfg); err == nil {
-		t.Errorf("expected an error due to nil RiakAuthoptions in the config but, go no error.")
-	}
-
-	cfg.RiakAuthOptions = &riak.AuthOptions{
-		User:      "riakuser",
-		Password:  "password",
-		TlsConfig: &tls.Config{},
-	}
-
-	if _, err := getRiakCluster(db, cfg); err != nil {
-		t.Errorf("expected no errors, actual: %s.", err)
-	}
-
-	rows2 := sqlmock.NewRows([]string{"s.host_name", "s.domain_name"})
-	mock.ExpectQuery("SELECT").WillReturnRows(rows2)
-
-	if _, err := getRiakCluster(db, cfg); err == nil {
-		t.Errorf("expected an error due to no available riak servers.")
-	}
-}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services