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

zrhoffman 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 8fb776b960 Add the ability to traffic Router to customize default 
https cert (#7135)
8fb776b960 is described below

commit 8fb776b9600dfbcd2ca9ae219af74f233fe19852
Author: Srijeet Chatterjee <[email protected]>
AuthorDate: Fri Oct 14 08:15:19 2022 -0600

    Add the ability to traffic Router to customize default https cert (#7135)
    
    * Add the ability to traffic Router to customize default https cert
    
    * Add license
    
    * Add debug
    
    * stacktrace
    
    * fix error log
    
    * fix error log
    
    * fix tests
---
 CHANGELOG.md                                       |  1 +
 .../traffic_router/traffic_router_api.rst          | 27 ++++++++++++++
 .../traffic_router/secure/CertificateRegistry.java | 41 ++++++++++++++++------
 .../traffic_router/utils/HttpsProperties.java      | 38 +++++++++++++++-----
 .../connector/src/test/java/conf/https.properties  | 24 +++++++++++++
 .../src/test/java/utils/HttpsPropertiesTest.java   | 41 ++++++++++++++++++++++
 6 files changed, 154 insertions(+), 18 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f83dac81e7..20535d27b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ## [unreleased]
 ### Added
+- [#7089](https://github.com/apache/trafficcontrol/issues/7089) *Traffic 
Router* Added the ability to specify HTTPS certificate attributes.
 - [#7109](https://github.com/apache/trafficcontrol/pull/7109) *Traffic Router* 
Removed `dnssec.zone.diffing.enabled` and `dnssec.rrsig.cache.enabled` 
parameters.
 - [#7075](https://github.com/apache/trafficcontrol/pull/7075) *Traffic Portal* 
Added the `lastUpdated` field to all delivery service forms.
 - [#7055](https://github.com/apache/trafficcontrol/issues/7055) *Traffic 
Portal* Made `Clear Table Filters` option visible to the user.
diff --git a/docs/source/development/traffic_router/traffic_router_api.rst 
b/docs/source/development/traffic_router/traffic_router_api.rst
index b6047d1ebd..fc6e4cdc8a 100644
--- a/docs/source/development/traffic_router/traffic_router_api.rst
+++ b/docs/source/development/traffic_router/traffic_router_api.rst
@@ -27,6 +27,33 @@ To override the self signed certificates with new ones from 
a certificate author
 The API can be configured via HTTPS on port 3443 in 
:file:`/opt/traffic_router/conf/server.xml` or by setting a :term:`Parameter` 
named ``secure.api.port`` with ``configFile`` ``server.xml`` on the Traffic 
Router's :term:`Profile`.  When ``systemctl start traffic_router`` is run, it 
will generate self signed certificates at ``/opt/traffic_router/conf/``, create 
a new Java Keystore named :file:`/opt/traffic_router/conf/keyStore.jks`, and 
add the new certificate to the Keystore.  The passw [...]
 To override the self signed certificates with new ones from a certificate 
authority, either replace the Java Keystore in the default location or update 
the properties for the new Keystore location and password at 
:file:`/opt/traffic_router/conf/https.properties` and then restart the Traffic 
Router using ``systemctl``.
 
+Other attributes of the default certificate can also be customized by 
specifying appropriate values for the following properties in 
:file:`/opt/traffic_router/conf/https.properties`. These properties are listed 
below:
+
+.. table:: HTTPS Certificate Attributes
+
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       | Name                                     | Description                
                                            | Default                           
                      |
+       
+==========================================+========================================================================+=========================================================+
+       |  https.certificate.location              | The location of the 
certificate key store                              |                            
                             |
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       |  https.password                          | The password for the 
certificate key store                             |                             
                            |
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       |  https.key.size                          | The size for the HTTPS 
keys                                            | 2048                          
                          |
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       |  https.signature.algorithm               | The HTTPS signing 
algorithm to be used                                 | SHA1WithRSA              
                               |
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       |  https.validity.years                    | The amount of time (in 
years) for which the cert is valid              | 3                             
                          |
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       |  https.certificate.country               | The country of the 
certificate                                         | US                        
                              |
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       |  https.certificate.state                 | The state of the 
certificate                                           | CO                      
                                |
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       |  https.certificate.locality              | The locality of the 
certificate                                        | Denver                     
                             |
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       |  https.certificate.organization          | The organization of the 
certificate                                    | Apache Traffic Control         
                         |
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
+       |  https.certificate.organizational.unit   | The organizational unit of 
the certificate                             | Apache Foundation, Hosted by 
Traffic Control, CDNDefault|
+       
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
 
 Traffic Router API endpoints only respond to ``GET`` requests.
 
diff --git 
a/traffic_router/connector/src/main/java/org/apache/traffic_control/traffic_router/secure/CertificateRegistry.java
 
b/traffic_router/connector/src/main/java/org/apache/traffic_control/traffic_router/secure/CertificateRegistry.java
index c9d1514ab3..ded6481c62 100644
--- 
a/traffic_router/connector/src/main/java/org/apache/traffic_control/traffic_router/secure/CertificateRegistry.java
+++ 
b/traffic_router/connector/src/main/java/org/apache/traffic_control/traffic_router/secure/CertificateRegistry.java
@@ -55,6 +55,15 @@ import java.util.List;
 import java.util.Map;
 
 public class CertificateRegistry {
+       private static final String HTTPS_PROPERTIES_FILE = 
"/opt/traffic_router/conf/https.properties";
+       private static final String HTTPS_KEY_SIZE = "https.key.size";
+       private static final String HTTPS_SIGNATURE_ALGORITHM = 
"https.signature.algorithm";
+       private static final String HTTPS_VALIDITY_YEARS = 
"https.validity.years";
+       private static final String HTTPS_CERTIFICATE_COUNTRY = 
"https.certificate.country";
+       private static final String HTTPS_CERTIFICATE_STATE = 
"https.certificate.state";
+       private static final String HTTPS_CERTIFICATE_LOCALITY = 
"https.certificate.locality";
+       private static final String HTTPS_CERTIFICATE_ORGANIZATION = 
"https.certificate.organization";
+       private static final String HTTPS_CERTIFICATE_OU = 
"https.certificate.organizational.unit";
        public static final String DEFAULT_SSL_KEY = "default.invalid";
        private static final Logger log = 
LogManager.getLogger(CertificateRegistry.class);
        private CertificateDataConverter certificateDataConverter = new 
CertificateDataConverter();
@@ -80,8 +89,23 @@ public class CertificateRegistry {
        @SuppressWarnings({"PMD.UseArrayListInsteadOfVector", 
"PMD.AvoidUsingHardCodedIP"})
        private static HandshakeData createDefaultSsl() {
                try {
+                       final Map<String, String> httpsProperties = (new 
HttpsProperties(HTTPS_PROPERTIES_FILE)).getHttpsPropertiesMap();
                        final KeyPairGenerator keyPairGenerator = 
KeyPairGenerator.getInstance("RSA");
-                       keyPairGenerator.initialize(2048);
+                       int keysize = 2048, validityLength = 3;
+                       String country = "US", state = "CO", locality = 
"Denver", organization = "Apache Traffic Control",
+                                       organizationalUnit = ";OU=Apache 
Foundation; OU=Hosted by Traffic Control; OU=CDNDefault",
+                                       signingAlgorithm = "SHA1WithRSA";
+                       if (httpsProperties != null) {
+                               keysize = 
Integer.parseInt(httpsProperties.getOrDefault(HTTPS_KEY_SIZE, 
String.valueOf(keysize)));
+                               country = 
httpsProperties.getOrDefault(HTTPS_CERTIFICATE_COUNTRY, country);
+                               state = 
httpsProperties.getOrDefault(HTTPS_CERTIFICATE_STATE, state);
+                               locality = 
httpsProperties.getOrDefault(HTTPS_CERTIFICATE_LOCALITY, locality);
+                               organization = 
httpsProperties.getOrDefault(HTTPS_CERTIFICATE_ORGANIZATION, organization);
+                               organizationalUnit = 
httpsProperties.getOrDefault(HTTPS_CERTIFICATE_OU, organizationalUnit);
+                               validityLength = 
Integer.parseInt(httpsProperties.getOrDefault(HTTPS_VALIDITY_YEARS, 
String.valueOf(validityLength)));
+                               signingAlgorithm = 
httpsProperties.getOrDefault(HTTPS_SIGNATURE_ALGORITHM, signingAlgorithm);
+                       }
+                       keyPairGenerator.initialize(keysize);
                        final KeyPair keyPair = 
keyPairGenerator.generateKeyPair();
 
                        //Generate self signed certificate
@@ -93,20 +117,17 @@ public class CertificateRegistry {
                        // Generate cert details
                        final long now = System.currentTimeMillis();
                        final Date startDate = new 
Date(System.currentTimeMillis());
-
-                       final X500Name dnName = new X500Name("C=US; ST=CO; 
L=Denver; " +
-                                       "O=Apache Traffic Control; OU=Apache 
Foundation; OU=Hosted by Traffic Control; " +
-                                       "OU=CDNDefault; CN="+DEFAULT_SSL_KEY);
+                       final String certAttributes = "C=" + country + "; ST=" 
+ state + "; L=" + locality + "; O=" + organization + organizationalUnit + "; 
CN=" + DEFAULT_SSL_KEY;
+                       final X500Name dnName = new X500Name(certAttributes);
                        final BigInteger certSerialNumber = new 
BigInteger(Long.toString(now));
 
                        final Calendar calendar = Calendar.getInstance();
                        calendar.setTime(startDate);
-                       calendar.add(Calendar.YEAR, 3);
+                       calendar.add(Calendar.YEAR, validityLength);
 
                        final Date endDate = calendar.getTime();
-
                        // Build certificate
-                       final ContentSigner contentSigner = new 
JcaContentSignerBuilder("SHA1WithRSA").build(keyPair.getPrivate());
+                       final ContentSigner contentSigner = new 
JcaContentSignerBuilder(signingAlgorithm).build(keyPair.getPrivate());
 
                        final JcaX509v3CertificateBuilder certBuilder = new 
JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, 
dnName, keyPair.getPublic());
 
@@ -128,7 +149,7 @@ public class CertificateRegistry {
                        return new HandshakeData(DEFAULT_SSL_KEY, 
DEFAULT_SSL_KEY, chain, keyPair.getPrivate());
                }
                catch (Exception e) {
-                       log.error("Could not generate the default certificate: 
"+e.getMessage(),e);
+                       log.error("Could not generate the default certificate: 
", e);
                        return null;
                }
        }
@@ -151,7 +172,7 @@ public class CertificateRegistry {
 
        private HandshakeData createApiDefaultSsl() {
                try {
-                       final Map<String, String> httpsProperties = (new 
HttpsProperties()).getHttpsPropertiesMap();
+                       final Map<String, String> httpsProperties = (new 
HttpsProperties(HTTPS_PROPERTIES_FILE)).getHttpsPropertiesMap();
 
                        final KeyStore ks = KeyStore.getInstance("JKS");
                        final String selfSignedKeystoreFile = 
httpsProperties.get("https.certificate.location");
diff --git 
a/traffic_router/connector/src/main/java/org/apache/traffic_control/traffic_router/utils/HttpsProperties.java
 
b/traffic_router/connector/src/main/java/org/apache/traffic_control/traffic_router/utils/HttpsProperties.java
index 1e1a297e6c..d4f19141fd 100644
--- 
a/traffic_router/connector/src/main/java/org/apache/traffic_control/traffic_router/utils/HttpsProperties.java
+++ 
b/traffic_router/connector/src/main/java/org/apache/traffic_control/traffic_router/utils/HttpsProperties.java
@@ -25,29 +25,51 @@ import java.util.Map;
 
 public class HttpsProperties {
     private static final Logger log = 
LogManager.getLogger(HttpsProperties.class);
-    private static final String HTTPS_PROPERTIES_FILE = 
"/opt/traffic_router/conf/https.properties";
+    private static final String HTTPS_CERTIFICATE_OU = 
"https.certificate.organizational.unit";
+
     private final Map<String, String> httpsPropertiesMap;
 
-    public HttpsProperties() {
-        this.httpsPropertiesMap = loadHttpsProperties();
+    public HttpsProperties(final String fileName) {
+        this.httpsPropertiesMap = loadHttpsProperties(fileName);
     }
 
     public Map<String, String> getHttpsPropertiesMap() {
         return httpsPropertiesMap;
     }
 
-    private static Map<String, String> loadHttpsProperties() {
+    private static Map<String, String> loadHttpsProperties(final String 
fileName) {
         try {
             final Map<String, String> httpsProperties = new HashMap<>();
-            
Files.readAllLines(Paths.get(HTTPS_PROPERTIES_FILE)).forEach(propString -> {
+            Files.readAllLines(Paths.get(fileName)).forEach(propString -> {
                 if (!propString.startsWith("#")) { // Ignores comments in 
properties file
-                    final String[] prop = propString.split("=");
-                    httpsProperties.put(prop[0], prop[1]);
+                    final String[] props = propString.split("=");
+                    if (props.length < 2) {
+                        log.error("Property malformed, should be in the form 
key=value");
+                    } else {
+                        final String key = props[0];
+                        final String val = props[1];
+                        if (key.equals(HTTPS_CERTIFICATE_OU)) {
+                            if (val.equals("") || val.length() < 2) {
+                                log.error("Malformed " + HTTPS_CERTIFICATE_OU 
+ " property value");
+                            } else {
+                                final String[] orgUnits = val.split(",");
+                                String organizationalUnit = "";
+                                StringBuilder sb = new 
StringBuilder(organizationalUnit);
+                                for (final String ou : orgUnits) {
+                                    sb = sb.append("; OU=" + ou);
+                                }
+                                organizationalUnit = sb.toString();
+                                httpsProperties.put(key, organizationalUnit);
+                            }
+                        } else {
+                            httpsProperties.put(key, val);
+                        }
+                    }
                 }
             });
             return httpsProperties;
         } catch (Exception e) {
-            log.error("Error loading https properties file.");
+            log.error("Error loading https properties file at "+ fileName+ ", 
error: " +e.getMessage());
             return null;
         }
     }
diff --git a/traffic_router/connector/src/test/java/conf/https.properties 
b/traffic_router/connector/src/test/java/conf/https.properties
new file mode 100644
index 0000000000..2c781e5ba8
--- /dev/null
+++ b/traffic_router/connector/src/test/java/conf/https.properties
@@ -0,0 +1,24 @@
+#
+#
+# Licensed 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.
+#
+https.certificate.location=/opt/traffic_router/conf/keyStore.jks
+https.password=changeit
+https.key.size=1024
+https.signature.algorithm=TestAlgorithm
+https.validity.years=TestValidity
+https.certificate.country=TestCountry
+https.certificate.state=TestState
+https.certificate.locality=TestLocality
+https.certificate.organization=TestOrg
+https.certificate.organizational.unit=Test Org Unit, Test Org Unit 2
\ No newline at end of file
diff --git 
a/traffic_router/connector/src/test/java/utils/HttpsPropertiesTest.java 
b/traffic_router/connector/src/test/java/utils/HttpsPropertiesTest.java
new file mode 100644
index 0000000000..3683e076e6
--- /dev/null
+++ b/traffic_router/connector/src/test/java/utils/HttpsPropertiesTest.java
@@ -0,0 +1,41 @@
+/*
+ *
+ * Licensed 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.
+ */
+
+package utils;
+
+import org.apache.traffic_control.traffic_router.utils.HttpsProperties;
+import org.junit.Test;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import java.util.Map;
+
+public class HttpsPropertiesTest {
+    @Test
+    public void checkGetHttpsProperties() throws Exception {
+        final String fileName = "src/test/java/conf/https.properties";
+        HttpsProperties httpsProperties = new HttpsProperties(fileName);
+        Map<String, String> propsMap = httpsProperties.getHttpsPropertiesMap();
+        assertThat(propsMap.get("https.certificate.location"), 
equalTo("/opt/traffic_router/conf/keyStore.jks"));
+        assertThat(propsMap.get("https.password"), equalTo("changeit"));
+        assertThat(propsMap.get("https.key.size"), equalTo("1024"));
+        assertThat(propsMap.get("https.signature.algorithm"), 
equalTo("TestAlgorithm"));
+        assertThat(propsMap.get("https.validity.years"), 
equalTo("TestValidity"));
+        assertThat(propsMap.get("https.certificate.country"), 
equalTo("TestCountry"));
+        assertThat(propsMap.get("https.certificate.state"), 
equalTo("TestState"));
+        assertThat(propsMap.get("https.certificate.locality"), 
equalTo("TestLocality"));
+        assertThat(propsMap.get("https.certificate.organization"), 
equalTo("TestOrg"));
+        assertThat(propsMap.get("https.certificate.organizational.unit"), 
equalTo("; OU=Test Org Unit; OU= Test Org Unit 2"));
+    }
+}

Reply via email to