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
+}