You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by zr...@apache.org on 2021/09/21 22:12:00 UTC

[trafficcontrol] branch 6.0.x updated: Generate a self signed cert on ds creation/update (#6024) (#6231)

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

zrhoffman pushed a commit to branch 6.0.x
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/6.0.x by this push:
     new e3c7697  Generate a self signed cert on ds creation/update (#6024) (#6231)
e3c7697 is described below

commit e3c769778af1ed6c53550999e98b047967395108
Author: Zach Hoffman <zr...@apache.org>
AuthorDate: Tue Sep 21 16:11:51 2021 -0600

    Generate a self signed cert on ds creation/update (#6024) (#6231)
    
    * Generate a self signed cert on ds creation/update
    
    * Added nil check for example urls, fixed test that was failing
    
    * updated per comments
    
    * fixed tests
    
    * more test fixing
    
    * added default cert verification test
    
    * added default cert config to cdn.conf
    
    * updated gofmt
    
    * added nil check
    
    * added default cert config validation and updated db tx use
    
    (cherry picked from commit 7d0472dc25dc4c3da79a0e1eb8dd55ebd0fdaec0)
    
    Co-authored-by: mattjackson220 <33...@users.noreply.github.com>
---
 CHANGELOG.md                                       |  1 +
 docs/source/admin/traffic_ops.rst                  |  8 +++
 traffic_ops/app/conf/cdn.conf                      |  9 ++-
 .../testing/api/v4/deliveryservices_test.go        | 40 ++++++++++-
 traffic_ops/traffic_ops_golang/config/config.go    | 36 +++++++++-
 .../traffic_ops_golang/dbhelpers/db_helpers.go     | 11 +++
 .../deliveryservice/deliveryservices.go            | 17 +++++
 .../traffic_ops_golang/deliveryservice/sslkeys.go  | 80 +++++++++++++++++++++-
 8 files changed, 196 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f060218..54eb944 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -107,6 +107,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - [#5129](https://github.com/apache/trafficcontrol/issues/5129) - Updated TM so that it returns a 404 if the endpoint is not supported.
 - Fixed Traffic Router crs/stats to prevent overflow and to correctly record the time used in averages.
 - [#6093](https://github.com/apache/trafficcontrol/issues/6093) - Fixed Let's Encrypt to work for delivery services where the domain does not contain the XMLID.
+- [#5893](https://github.com/apache/trafficcontrol/issues/5893) - A self signed certificate is created when an HTTPS delivery service is created or an HTTP delivery service is updated to HTTPS.
 
 ### Changed
 - Migrated completely off of bower in favor of npm
diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst
index b1df31e..ff08a24 100644
--- a/docs/source/admin/traffic_ops.rst
+++ b/docs/source/admin/traffic_ops.rst
@@ -315,6 +315,14 @@ This file deals with the configuration parameters of running Traffic Ops itself.
 	:renew_days_before_expiration: Set the number of days before expiration date to renew certificates.
 	:summary_email: The email address to use for summarizing certificate expiration and renewal status. If it is blank, no email will be sent.
 
+:default_certificate_info: This is an optional object to define default values when generating a self signed certificate when an HTTPS delivery service is created or updated. If this is an empty object or not present in the :ref:`cdn.conf` then the term "Placeholder" will be used for all fields.
+
+ 	:business_unit: An optional field which, if present, will represent the business unit for which the SSL certificate was generated
+	:city: An optional field which, if present, will represent the resident city of the generated SSL certificate
+	:organization: An optional field which, if present, will represent the organization for which the SSL certificate was generated
+	:country: An optional field which, if present, will represent the resident country of the generated SSL certificate
+	:state: An optional field which, if present, will represent the resident state or province of the generated SSL certificate
+
 :geniso: This object contains configuration options for system ISO generation.
 
 	:iso_root_path: Sets the filesystem path to the root of the ISO generation directory. For default installations, this should usually be set to :file:`/opt/traffic_ops/app/public`.
diff --git a/traffic_ops/app/conf/cdn.conf b/traffic_ops/app/conf/cdn.conf
index 1a78e51..bf3a7ec7 100644
--- a/traffic_ops/app/conf/cdn.conf
+++ b/traffic_ops/app/conf/cdn.conf
@@ -89,5 +89,12 @@
             "kid" : "",
             "hmac_encoded" : ""
         }
-    ]
+    ],
+    "default_certificate_info" : {
+        "business_unit" : "",
+        "city" : "",
+        "organization" : "",
+        "country" : "",
+        "state" : ""
+    }
 }
diff --git a/traffic_ops/testing/api/v4/deliveryservices_test.go b/traffic_ops/testing/api/v4/deliveryservices_test.go
index 86c64d9..8a8e535 100644
--- a/traffic_ops/testing/api/v4/deliveryservices_test.go
+++ b/traffic_ops/testing/api/v4/deliveryservices_test.go
@@ -42,6 +42,7 @@ func TestDeliveryServices(t *testing.T) {
 		header.Set(rfc.IfModifiedSince, ti)
 		header.Set(rfc.IfUnmodifiedSince, ti)
 		if includeSystemTests {
+			t.Run("Verify SSL key generation on DS creation", VerifySSLKeysOnDsCreationTest)
 			t.Run("Update CDN for a Delivery Service with SSL keys", SSLDeliveryServiceCDNUpdateTest)
 			t.Run("Create URL Signature keys for a Delivery Service", CreateTestDeliveryServicesURLSignatureKeys)
 			t.Run("Retrieve URL Signature keys for a Delivery Service", GetTestDeliveryServicesURLSignatureKeys)
@@ -135,7 +136,7 @@ func CUDDeliveryServiceWithLocks(t *testing.T) {
 	if len(types.Response) < 1 {
 		t.Fatal("expected at least one type")
 	}
-	customDS := getCustomDS(cdn.ID, types.Response[0].ID, "cdn_locks_test_ds_name", "routingName", "https://test_cdn_locks.com", "cdn_locks_test_ds_xml_id")
+	customDS := getCustomDS(cdn.ID, types.Response[0].ID, "cdn-locks-test-ds-name", "edge", "https://test-cdn-locks.com", "cdn-locks-test-ds-xml-id")
 
 	// Create a lock for this user
 	_, _, err = userSession.CreateCDNLock(tc.CDNLock{
@@ -704,6 +705,43 @@ func DeliveryServiceSSLKeys(t *testing.T) {
 	}
 }
 
+func VerifySSLKeysOnDsCreationTest(t *testing.T) {
+	for _, ds := range testData.DeliveryServices {
+		if !(*ds.Protocol == tc.DSProtocolHTTPS || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS || *ds.Protocol == tc.DSProtocolHTTPToHTTPS) {
+			continue
+		}
+		var err error
+		dsSSLKey := new(tc.DeliveryServiceSSLKeys)
+		for tries := 0; tries < 5; tries++ {
+			time.Sleep(time.Second)
+			var sslKeysResp tc.DeliveryServiceSSLKeysResponse
+			sslKeysResp, _, err = TOSession.GetDeliveryServiceSSLKeys(*ds.XMLID, client.RequestOptions{})
+			*dsSSLKey = sslKeysResp.Response
+			if err == nil && dsSSLKey != nil {
+				break
+			}
+		}
+
+		if err != nil || dsSSLKey == nil {
+			t.Fatalf("unable to get DS %s SSL key: %v", *ds.XMLID, err)
+		}
+		if dsSSLKey.Certificate.Key == "" {
+			t.Errorf("expected a valid key but got nothing")
+		}
+		if dsSSLKey.Certificate.Crt == "" {
+			t.Errorf("expected a valid certificate, but got nothing")
+		}
+		if dsSSLKey.Certificate.CSR == "" {
+			t.Errorf("expected a valid CSR, but got nothing")
+		}
+
+		err = deliveryservice.Base64DecodeCertificate(&dsSSLKey.Certificate)
+		if err != nil {
+			t.Fatalf("couldn't decode certificate: %v", err)
+		}
+	}
+}
+
 func SSLDeliveryServiceCDNUpdateTest(t *testing.T) {
 	cdnNameOld := "sslkeytransfer"
 	oldCdn := createBlankCDN(cdnNameOld, t)
diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go
index 3041441..b7be58a 100644
--- a/traffic_ops/traffic_ops_golang/config/config.go
+++ b/traffic_ops/traffic_ops_golang/config/config.go
@@ -58,7 +58,9 @@ type Config struct {
 	InfluxEnabled          bool
 	InfluxDBConfPath       string `json:"influxdb_conf_path"`
 	Version                string
-	UseIMS                 bool `json:"use_ims"`
+	UseIMS                 bool                    `json:"use_ims"`
+	RoleBasedPermissions   bool                    `json:"role_based_permissions"`
+	DefaultCertificateInfo *DefaultCertificateInfo `json:"default_certificate_info"`
 }
 
 // ConfigHypnotoad carries http setting for hypnotoad (mojolicious) server
@@ -172,6 +174,38 @@ type ConfigAcmeAccount struct {
 	HmacEncoded  string `json:"hmac_encoded"`
 }
 
+type DefaultCertificateInfo struct {
+	BusinessUnit string `json:"business_unit"`
+	City         string `json:"city"`
+	Organization string `json:"organization"`
+	Country      string `json:"country"`
+	State        string `json:"state"`
+}
+
+func (d *DefaultCertificateInfo) Validate() (error, bool) {
+	missingList := []string{}
+	if d.BusinessUnit == "" {
+		missingList = append(missingList, "BusinessUnit")
+	}
+	if d.City == "" {
+		missingList = append(missingList, "City")
+	}
+	if d.Organization == "" {
+		missingList = append(missingList, "Organization")
+	}
+	if d.Country == "" {
+		missingList = append(missingList, "Country")
+	}
+	if d.State == "" {
+		missingList = append(missingList, "State")
+	}
+
+	if len(missingList) != 0 {
+		return fmt.Errorf("default certificate information is missing: %s", missingList), false
+	}
+	return nil, true
+}
+
 // ConfigDatabase reflects the structure of the database.conf file
 type ConfigDatabase struct {
 	Description string `json:"description"`
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index f3f35e6..8d70fc3 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -1603,3 +1603,14 @@ func GetDSIDFromStaticDNSEntry(tx *sql.Tx, staticDNSEntryID int) (int, error) {
 	}
 	return dsID, nil
 }
+
+// GetCDNNameDomain returns the name and domain for a given CDN ID.
+func GetCDNNameDomain(cdnID int, tx *sql.Tx) (string, string, error) {
+	q := `SELECT cdn.name, cdn.domain_name from cdn where cdn.id = $1`
+	cdnName := ""
+	cdnDomain := ""
+	if err := tx.QueryRow(q, cdnID).Scan(&cdnName, &cdnDomain); err != nil {
+		return "", "", fmt.Errorf("getting cdn name and domain for cdn '%v': "+err.Error(), cdnID)
+	}
+	return cdnName, cdnDomain, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
index 77d71ed..7a675fb 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
@@ -219,6 +219,7 @@ func CreateV40(w http.ResponseWriter, r *http.Request) {
 	}
 	alerts := res.TLSVersionsAlerts()
 	alerts.AddNewAlert(tc.SuccessLevel, "Delivery Service creation was successful")
+
 	w.Header().Set("Location", fmt.Sprintf("/api/4.0/deliveryservices?id=%d", *res.ID))
 	api.WriteAlertsObj(w, r, http.StatusCreated, alerts, []tc.DeliveryServiceV40{*res})
 }
@@ -548,6 +549,13 @@ func createV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV40 t
 
 	dsV40 = ds
 
+	if inf.Config.TrafficVaultEnabled && ds.Protocol != nil && (*ds.Protocol == tc.DSProtocolHTTPS || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS || *ds.Protocol == tc.DSProtocolHTTPToHTTPS) {
+		err, errCode := GeneratePlaceholderSelfSignedCert(dsV40, inf, r.Context())
+		if err != nil || errCode != http.StatusOK {
+			return nil, errCode, nil, fmt.Errorf("creating self signed default cert: %v", err)
+		}
+	}
+
 	return &dsV40, http.StatusOK, nil, nil
 }
 
@@ -715,6 +723,7 @@ func UpdateV40(w http.ResponseWriter, r *http.Request) {
 	}
 	alerts := res.TLSVersionsAlerts()
 	alerts.AddNewAlert(tc.SuccessLevel, "Delivery Service update was successful")
+
 	api.WriteAlertsObj(w, r, http.StatusOK, alerts, []tc.DeliveryServiceV40{*res})
 }
 
@@ -1124,6 +1133,14 @@ func updateV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV40 *
 	}
 
 	dsV40 = (*tc.DeliveryServiceV40)(&ds)
+
+	if inf.Config.TrafficVaultEnabled && ds.Protocol != nil && (*ds.Protocol == tc.DSProtocolHTTPS || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS || *ds.Protocol == tc.DSProtocolHTTPToHTTPS) {
+		err, errCode := GeneratePlaceholderSelfSignedCert(*dsV40, inf, r.Context())
+		if err != nil || errCode != http.StatusOK {
+			return nil, errCode, nil, fmt.Errorf("creating self signed default cert: %v", err)
+		}
+	}
+
 	return dsV40, http.StatusOK, nil, nil
 }
 
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go b/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go
index a1a7f16..d1b1f2e 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go
@@ -25,9 +25,12 @@ import (
 	"errors"
 	"net/http"
 	"strconv"
+	"strings"
 
 	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+	"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/tenant"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault"
@@ -70,7 +73,7 @@ func GenerateSSLKeys(w http.ResponseWriter, r *http.Request) {
 		api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
 		return
 	}
-	if err := generatePutRiakKeys(req, inf.Tx.Tx, inf.Vault, r.Context()); err != nil {
+	if err := generatePutTrafficVaultSSLKeys(req, inf.Tx.Tx, inf.Vault, r.Context()); err != nil {
 		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("generating and putting SSL keys: "+err.Error()))
 		return
 	}
@@ -82,9 +85,9 @@ func GenerateSSLKeys(w http.ResponseWriter, r *http.Request) {
 	api.WriteResp(w, r, "Successfully created ssl keys for "+*req.DeliveryService)
 }
 
-// generatePutRiakKeys generates a certificate, csr, and key from the given request, and insert it into the Riak key database.
+// generatePutTrafficVaultSSLKeys generates a certificate, csr, and key from the given request, and insert it into the Riak key database.
 // The req MUST be validated, ensuring required fields exist.
-func generatePutRiakKeys(req tc.DeliveryServiceGenSSLKeysReq, tx *sql.Tx, tv trafficvault.TrafficVault, ctx context.Context) error {
+func generatePutTrafficVaultSSLKeys(req tc.DeliveryServiceGenSSLKeysReq, tx *sql.Tx, tv trafficvault.TrafficVault, ctx context.Context) error {
 	dsSSLKeys := tc.DeliveryServiceSSLKeys{
 		CDN:             *req.CDN,
 		DeliveryService: *req.DeliveryService,
@@ -110,3 +113,74 @@ func generatePutRiakKeys(req tc.DeliveryServiceGenSSLKeysReq, tx *sql.Tx, tv tra
 	}
 	return nil
 }
+
+// GeneratePlaceholderSelfSignedCert generates a self-signed SSL certificate as a placeholder when a new HTTPS
+// delivery service is created or an HTTP delivery service is updated to use HTTPS.
+func GeneratePlaceholderSelfSignedCert(ds tc.DeliveryServiceV4, inf *api.APIInfo, context context.Context) (error, int) {
+	tx := inf.Tx.Tx
+	tv := inf.Vault
+	_, ok, err := tv.GetDeliveryServiceSSLKeys(*ds.XMLID, "", tx, context)
+	if err != nil {
+		return errors.New("getting latest ssl keys for xmlId: " + *ds.XMLID + " : " + err.Error()), http.StatusInternalServerError
+	}
+	if ok {
+		return nil, http.StatusOK
+	}
+
+	version := util.JSONIntStr(1)
+
+	cdnName, cdnDomain, err := dbhelpers.GetCDNNameDomain(*ds.CDNID, tx)
+	if err != nil {
+		return err, http.StatusInternalServerError
+	}
+
+	cdnNameStr := string(cdnName)
+
+	if ds.ExampleURLs == nil {
+		ds.ExampleURLs = MakeExampleURLs(ds.Protocol, *ds.Type, *ds.RoutingName, *ds.MatchList, cdnDomain)
+	}
+
+	hostname := strings.Split(ds.ExampleURLs[0], "://")[1]
+	if ds.Type.IsHTTP() {
+		parts := strings.Split(hostname, ".")
+		parts[0] = "*"
+		hostname = strings.Join(parts, ".")
+	}
+
+	req := tc.DeliveryServiceGenSSLKeysReq{
+		DeliveryServiceSSLKeysReq: tc.DeliveryServiceSSLKeysReq{
+			CDN:             &cdnNameStr,
+			DeliveryService: ds.XMLID,
+			HostName:        &hostname,
+			Key:             ds.XMLID,
+			Version:         &version,
+			BusinessUnit:    util.StrPtr("Placeholder"),
+			City:            util.StrPtr("Placeholder"),
+			Organization:    util.StrPtr("Placeholder"),
+			Country:         util.StrPtr("Placeholder"),
+			State:           util.StrPtr("Placeholder"),
+		},
+	}
+
+	if (inf.Config.DefaultCertificateInfo != nil && *inf.Config.DefaultCertificateInfo != config.DefaultCertificateInfo{}) {
+		defaultCertInfo := inf.Config.DefaultCertificateInfo
+		if err, ok := defaultCertInfo.Validate(); !ok {
+			return err, http.StatusInternalServerError
+		}
+
+		req.BusinessUnit = &defaultCertInfo.BusinessUnit
+		req.City = &defaultCertInfo.City
+		req.Organization = &defaultCertInfo.Organization
+		req.Country = &defaultCertInfo.Country
+		req.State = &defaultCertInfo.State
+	}
+
+	if err := generatePutTrafficVaultSSLKeys(req, tx, inf.Vault, context); err != nil {
+		return errors.New("generating and putting SSL keys: " + err.Error()), http.StatusInternalServerError
+	}
+	if err := updateSSLKeyVersion(*req.DeliveryService, req.Version.ToInt64(), tx); err != nil {
+		return errors.New("generating SSL keys for delivery service '" + *req.DeliveryService + "': " + err.Error()), http.StatusInternalServerError
+	}
+
+	return nil, http.StatusOK
+}