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 7d0472d  Generate a self signed cert on ds creation/update (#6024)
7d0472d is described below

commit 7d0472dc25dc4c3da79a0e1eb8dd55ebd0fdaec0
Author: mattjackson220 <[email protected]>
AuthorDate: Tue Sep 21 16:02:50 2021 -0600

    Generate a self signed cert on ds creation/update (#6024)
    
    * 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
---
 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    | 37 +++++++++-
 .../traffic_ops_golang/dbhelpers/db_helpers.go     | 11 +++
 .../deliveryservice/deliveryservices.go            | 17 +++++
 .../traffic_ops_golang/deliveryservice/sslkeys.go  | 80 +++++++++++++++++++++-
 8 files changed, 196 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9976d92..6d5d88e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ### Fixed
 - Fixed Traffic Router crs/stats to prevent overflow and to correctly record 
the time used in averages.
+- [#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
 - Updated `t3c` to request less unnecessary deliveryservice-server assignment 
and invalidation jobs data via new query params supported by Traffic Ops
diff --git a/docs/source/admin/traffic_ops.rst 
b/docs/source/admin/traffic_ops.rst
index 2d4af77..e4b5765 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 b46bf81..f44c4a4 100644
--- a/traffic_ops/app/conf/cdn.conf
+++ b/traffic_ops/app/conf/cdn.conf
@@ -90,5 +90,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 d3187c8..09fdef1 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)
@@ -137,7 +138,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{
@@ -706,6 +707,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 af632f8..b7be58a 100644
--- a/traffic_ops/traffic_ops_golang/config/config.go
+++ b/traffic_ops/traffic_ops_golang/config/config.go
@@ -58,8 +58,9 @@ type Config struct {
        InfluxEnabled          bool
        InfluxDBConfPath       string `json:"influxdb_conf_path"`
        Version                string
-       UseIMS                 bool `json:"use_ims"`
-       RoleBasedPermissions   bool `json:"role_based_permissions"`
+       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
@@ -173,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
+}

Reply via email to