Here is the approach I have researched for this issue.

The basic requirements are

1. Enable TLS for the API endpoint in TR
2. Should be able to switch it on or off at a TR instance level
3. When TLS is on, the unencrypted API should not be accessible 
4. TP and TO should still be able to access the API when TLS is on

**General Approach**
The Traffic Router RPM will generate a cert for the TR host and a CA cert for 
signing the host cert.  The CA cert will be added to the server's trusted CA's 
and the TR host will be added to the local keystore.  The self-signed cert 
serves as the ‘_default_’ cert to initialize TR. Once initialized one of the 
keys from RIAK (if there are any) will be selected by TR as the default for the 
HTTPS Delivery Services. For the /crs API calls the self-signed key will remain 
the default key. The client can take the generated CA cert and add it to their 
trusted CA store if they want to SSL to the /crs API. The CA cert will also be 
added to the trust store on the TP servers so they can continue to call the 
/crs API. 
The TR RPM will not overwrite the self-signed cert or the CA cert if they 
already exist on the server from an earlier install, or if the operations team 
has replaced it with an official certificate from an established CA. This 
approach will also resolve an issue in Traffic Router where it will not start 
up properly when there are no certificates for HTTPS delivery services 
installed in Traffic Vault.

**Configuration**
To enable TLS on a specific server the server.xml will first need to be 
modified. Replace the Connector for port 3333 with the following:

`               <Connector port="3333" 
protocol="com.comcast.cdn.traffic_control.traffic_router.protocol.LanguidNioProtocol"
  maxThreads="10000"
                           scheme="https" secure="true" SSLEnabled="true" 
clientAuth="false" sslProtocol="TLS" connectionTimeout="10000"
                           mbeanPath="traffic-router:name=languidState" 
readyAttribute="Ready" portAttribute="ApiPort" sendReasonPhrase="true"          
                           
sslImplementationName="com.comcast.cdn.traffic_control.traffic_router.protocol.RouterSslImplementation">
</Connector>`

**TR Code Changes**
The Traffic Router code will definitely need to be modified. The suggested 
implementation would modify the source in the TR connector:
com.comcast.cdn.traffic_control.traffic_router.protocol.RouterNioEndpoint
com.comcast.cdn.traffic_control.traffic_router.secure.CertificateRegistry

In RouterNioEndpoint call the 'loadDefaultCert' method during initialization: 

    protected void initialiseSsl() throws Exception {
        if (isSSLEnabled()) {
            destroySsl();
            sslHostConfigs.clear();
            final KeyManager keyManager = new KeyManager();
            final CertificateRegistry certificateRegistry =  
keyManager.getCertificateRegistry();
            certificateRegistry.loadDefaultCert();
            replaceSSLHosts(certificateRegistry.getHandshakeData());

            //Now let initialiseSsl do it's thing.
            super.initialiseSsl();
            certificateRegistry.setEndPoint(this);
        }
    }

    synchronized private void replaceSSLHosts(final Map<String, HandshakeData> 
sslHostsData) {
        final Set<String> aliases = sslHostsData.keySet();
        boolean firstAlias = true;
        String lastHostName = "";

        for (final String alias : aliases) {
            final SSLHostConfig sslHostConfig = new SSLHostConfig();
            final SSLHostConfigCertificate cert = new 
SSLHostConfigCertificate(sslHostConfig, SSLHostConfigCertificate.Type.RSA);
            cert.setCertificateKeyAlias(alias);
            sslHostConfig.addCertificate(cert);
            sslHostConfig.setCertificateKeyAlias(alias);
            sslHostConfig.setHostName(sslHostsData.get(alias).getHostname());
            sslHostConfig.setProtocols("all");
            sslHostConfig.setConfigType(getSslConfigType());
            sslHostConfig.setCertificateVerification("none");
            LOGGER.info("sslHostConfig: "+sslHostConfig.getHostName()+" 
"+sslHostConfig.getTruststoreAlgorithm());

            if (!sslHostConfig.getHostName().equals(lastHostName)) {
                addSslHostConfig(sslHostConfig, true);
                lastHostName = sslHostConfig.getHostName();
            }

            if (firstAlias && ! "".equals(alias)) {
                // One of the configs must be set as the default
                setDefaultSSLHostConfigName(sslHostConfig.getHostName());
                firstAlias = false;
            }
            
            if (InetAddress.getLocalHost().getHostName().equals(alias))
            {
                setDefaultSSLHostConfigName(alias);
            }
        }

    }

 In CertificateRegistry add the 'loadDefaultCert' method and adjust the 
'importCertificateData' method:

        public void loadDefaultCert() throws Exception {
                final String hostkeyAlias = 
InetAddress.getLocalHost().getHostName();
                HandshakeData handshakeData = null;
                // Create the HandshakeData or load if it already exists
                if (getHandshakeData(hostkeyAlias) == null) {
                        KeyStore ks = KeyStore.getInstance("JKS");

                        if (new File(SELF_SIGNED_KEYSTORE_FILE).exists()) {
                                InputStream readStream = new 
FileInputStream(SELF_SIGNED_KEYSTORE_FILE);
                                ks.load(readStream, KEYSTORE_PASS);
                                readStream.close();
                                handshakeData = new HandshakeData("localhost", 
hostkeyAlias,
                                                
ks.getCertificateChain(hostkeyAlias), ks.getKey(hostKeyAlias));
                        } else {
                                handshakeData = generateSelfSignedHostCert();  
// this executes the Bash script for
                                // generating the default cert and its CA 
signing cert which should have been run
                                // during the RPM installation.
                        }

                        getHandshakeData().putIfAbsent(hostkeyAlias, 
handshakeData);
                }
                // else nothing to do

        }

        @SuppressWarnings("PMD.AccessorClassGeneration")
        private static class CertificateRegistryHolder {
                private static final CertificateRegistry 
DELIVERY_SERVICE_CERTIFICATES = new CertificateRegistry();
        }

        synchronized public void importCertificateDataList(final 
List<CertificateData> certificateDataList) {
                final Map<String, HandshakeData> changes = new HashMap<>();
                final Map<String, HandshakeData> master = new HashMap<>();

                // find CertificateData which has changed
                for (final CertificateData certificateData : 
certificateDataList) {
                        try {
                        final HandshakeData handshakeData = 
certificateDataConverter.toHandshakeData(certificateData);
                        final String alias = 
handshakeData.getHostname().replaceFirst("\\*\\.", "");
                        master.put(alias, handshakeData);

                        if 
(certificateData.equals(previousData.get(certificateData.getHostname()))) {
                                continue;
                        }
                        changes.put(alias, handshakeData);
                        log.warn("Imported handshake data with alias " + alias);
                } catch (Exception e) {
                                log.error("Failed to import certificate data 
for delivery service: '" + certificateData.getDeliveryservice() + "', hostname: 
'" + certificateData.getHostname() + "'");
                        }
                }

                // find CertificateData which has been removed
                for (final String hostname : previousData.keySet())
                {
                        if (!master.containsKey(hostname.replaceFirst("\\*\\.", 
"")) && sslEndpoint != null)
                        {
                                        
sslEndpoint.removeSslHostConfig(hostname);
                                    log.warn("Removed handshake data with 
hostname " + hostname);
                        }
                }

                // store the result for the next import
                previousData.clear();
                for (final CertificateData certificateData : 
certificateDataList) {
                        previousData.put(certificateData.getHostname(), 
certificateData);
                }

                String localHostKey = InetAddress.getLocalHost().getHostName();
                
master.putIfAbsent(localHostKey,handshakeDataMap.get(localHostKey));
                handshakeDataMap = master;

                if (sslEndpoint != null) {
                        sslEndpoint.reloadSSLHosts(changes);
                }

        }


**Traffic Ops Code Changes**
The Traffic Ops API will need to be modified so that it knows to use 'https' 
when constructing its URL for the Traffic Router API. These changes will most 
likely occur in the source files:
traffic_ops/app/lib/Utils/CCR.pm
traffic_ops/app/lib/UI/Cdn.pm

**Traffic Router RPM changes**
The RPM should generate the CA cert and self-signed cert. The 
traffic_router/core/src/main/scripts/preinstall.sh script is a good candidate. 
The Bash scripts written by Jeff Bevill for CDN in-a-box should be adaptable 
for this task. Preferably the scripts would be modified to add the self-signed 
key for the host to a Java keystore file in the /opt/traffic_router/ directory 
structure. They should not regenerate the certificates if they already exist on 
the file system. The RPM should also copy the CA certificate to the Traffic Ops 
servers and marked as a trusted CA where Perl and Go look for their CA 
certificates. 

[ Full content available at: 
https://github.com/apache/trafficcontrol/issues/2817 ]
This message was relayed via gitbox.apache.org for [email protected]

Reply via email to