DaanHoogland commented on code in PR #11468: URL: https://github.com/apache/cloudstack/pull/11468#discussion_r2290534093
########## core/src/main/java/com/cloud/network/HAProxyConfigurator.java: ########## @@ -469,46 +477,62 @@ private String getLbSubRuleForStickiness(final LoadBalancerTO lbTO) { return sb.toString(); } - private List<String> getRulesForPool(final LoadBalancerTO lbTO, final boolean keepAliveEnabled) { + private List<String> getRulesForPool(final LoadBalancerTO lbTO, final LoadBalancerConfigCommand lbCmd) { StringBuilder sb = new StringBuilder(); final String poolName = sb.append(lbTO.getSrcIp().replace(".", "_")).append('-').append(lbTO.getSrcPort()).toString(); final String publicIP = lbTO.getSrcIp(); final int publicPort = lbTO.getSrcPort(); final String algorithm = lbTO.getAlgorithm(); - final List<String> result = new ArrayList<String>(); - // add line like this: "listen 65_37_141_30-80\n\tbind 65.37.141.30:80" - sb = new StringBuilder(); - sb.append("listen ").append(poolName); - result.add(sb.toString()); + boolean sslOffloading = lbTO.getSslCert() != null && !lbTO.getSslCert().isRevoked() + && NetUtils.SSL_PROTO.equals(lbTO.getLbProtocol()); + + final List<String> frontendConfigs = new ArrayList<>(); + final List<String> backendConfigs = new ArrayList<>(); + final List<String> result = new ArrayList<>(); + sb = new StringBuilder(); sb.append("\tbind ").append(publicIP).append(":").append(publicPort); - result.add(sb.toString()); + + if (sslOffloading) { + sb.append(" ssl crt ").append(SSL_CERTS_DIR).append(poolName).append(".pem"); + // check for http2 support + sb.append(" alpn h2,http/1.1"); Review Comment: Can you explain this, @weizhouapache ? is `alpn h2` enough to do the check? And is the backend stretch then http 1.1? or is it just the default for the frontend? (maybe a link to docs?) ########## core/src/main/java/com/cloud/network/HAProxyConfigurator.java: ########## @@ -696,4 +728,26 @@ public String[][] generateFwRules(final LoadBalancerConfigCommand lbCmd) { return result; } + + @Override + public SslCertEntry[] generateSslCertEntries(LoadBalancerConfigCommand lbCmd) { + final Set<SslCertEntry> sslCertEntries = new HashSet<SslCertEntry>(); + for (final LoadBalancerTO lbTO : lbCmd.getLoadBalancers()) { + if (lbTO.getSslCert() != null) { + final LbSslCert cert = lbTO.getSslCert(); + if (cert.isRevoked()) { + continue; + } + if (lbTO.getLbProtocol() == null || ! lbTO.getLbProtocol().equals(NetUtils.SSL_PROTO)) { + continue; + } + StringBuilder sb = new StringBuilder(); + final String name = sb.append(lbTO.getSrcIp().replace(".", "_")).append('-').append(lbTO.getSrcPort()).toString(); + final SslCertEntry sslCertEntry = new SslCertEntry(name, cert.getCert(), cert.getKey(), cert.getChain(), cert.getPassword()); + sslCertEntries.add(sslCertEntry); Review Comment: ```suggestion addCertEntry(sslCertEntries, lbTO); ``` and then ``` private void addSslCertEntry(Collection<SslCertEntry> sslCertEntries, LoadBalancerTO lbTO) { final LbSslCert cert = lbTO.getSslCert(); if (cert.isRevoked()) { return; } if (lbTO.getLbProtocol() == null || ! lbTO.getLbProtocol().equals(NetUtils.SSL_PROTO)) { return; } StringBuilder sb = new StringBuilder(); final String name = lbTO.getSrcIp().replace(".", "_”) + ’-‘ + lbTO.getSrcPort()); sslCertEntries.add(new SslCertEntry(name, cert.getCert(), cert.getKey(), cert.getChain(), cert.getPassword())); } ``` sorry , I know I’m a nag and a purist…. ########## server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java: ########## @@ -1294,8 +1294,13 @@ public boolean assignCertToLoadBalancer(long lbRuleId, Long certId) { //check if the lb is already bound LoadBalancerCertMapVO certMapRule = _lbCertMapDao.findByLbRuleId(loadBalancer.getId()); - if (certMapRule != null) - throw new InvalidParameterValueException("Another certificate is already bound to the LB"); + if (certMapRule != null) { + if (!forced) { + throw new InvalidParameterValueException("Another certificate is already bound to the LB"); + } + logger.debug("Another certificate is already bound to the LB, removing it"); + removeCertFromLoadBalancer(lbRuleId); + } Review Comment: new method; validatenewMapRule(..)? ########## server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java: ########## @@ -929,6 +929,11 @@ public boolean validateHAProxyLBRule(final LoadBalancingRule rule) { return false; } + List<String> lbProtocols = Arrays.asList("tcp", "udp", "tcp-proxy", "ssl"); + if (rule.getLbProtocol() != null && !lbProtocols.contains(rule.getLbProtocol())) { + throw new InvalidParameterValueException("protocol " + rule.getLbProtocol() + " is not in valid protocols " + lbProtocols); + } Review Comment: new validation method.. ########## core/src/main/java/com/cloud/network/HAProxyConfigurator.java: ########## @@ -520,61 +544,69 @@ private List<String> getRulesForPool(final LoadBalancerTO lbTO, final boolean ke destsAvailable = true; } - Boolean httpbasedStickiness = false; + boolean httpbasedStickiness = false; /* attach stickiness sub rule only if the destinations are available */ - if (stickinessSubRule != null && destsAvailable == true) { + if (stickinessSubRule != null && destsAvailable) { for (final StickinessPolicyTO stickinessPolicy : lbTO.getStickinessPolicies()) { if (stickinessPolicy == null) { continue; } if (StickinessMethodType.LBCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName()) || StickinessMethodType.AppCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) { httpbasedStickiness = true; + break; } } if (httpbasedStickiness) { - result.addAll(dstWithCookieSubRule); + backendConfigs.addAll(dstWithCookieSubRule); } else { - result.addAll(dstSubRule); + backendConfigs.addAll(dstSubRule); } - result.add(stickinessSubRule); + backendConfigs.add(stickinessSubRule); } else { - result.addAll(dstSubRule); + backendConfigs.addAll(dstSubRule); } if (stickinessSubRule != null && !destsAvailable) { logger.warn("Haproxy stickiness policy for lb rule: " + lbTO.getSrcIp() + ":" + lbTO.getSrcPort() + ": Not Applied, cause: backends are unavailable"); } - if (publicPort == NetUtils.HTTP_PORT && !keepAliveEnabled || httpbasedStickiness) { - sb = new StringBuilder(); - sb.append("\t").append("mode http"); - result.add(sb.toString()); - sb = new StringBuilder(); - sb.append("\t").append("option httpclose"); - result.add(sb.toString()); + boolean keepAliveEnabled = lbCmd.keepAliveEnabled; + boolean http = (publicPort == NetUtils.HTTP_PORT && !keepAliveEnabled); + if (http || httpbasedStickiness || sslOffloading) { + frontendConfigs.add("\tmode http"); + String keepAliveLine = keepAliveEnabled ? "\tno option forceclose" : "\toption httpclose"; + frontendConfigs.add(keepAliveLine); } + // add line like this: "listen 65_37_141_30-80\n\tbind 65.37.141.30:80" + result.add(String.format("listen %s", poolName)); + result.addAll(frontendConfigs); + String cidrList = lbTO.getCidrList(); if (StringUtils.isNotBlank(cidrList)) { result.add(String.format("\tacl network_allowed src %s \n\ttcp-request connection reject if !network_allowed", cidrList)); } + result.addAll(backendConfigs); result.add(blankLine); return result; } private String generateStatsRule(final LoadBalancerConfigCommand lbCmd, final String ruleName, final String statsIp) { final StringBuilder rule = new StringBuilder("\nlisten ").append(ruleName).append("\n\tbind ").append(statsIp).append(":").append(lbCmd.lbStatsPort); // TODO DH: write test for this in both cases - if (!lbCmd.keepAliveEnabled) { - logger.info("Haproxy mode http enabled"); - rule.append("\n\tmode http\n\toption httpclose"); + rule.append("\n\tmode http"); + if (lbCmd.keepAliveEnabled) { + logger.info("Haproxy option http-keep-alive enabled"); + } else { + logger.info("Haproxy option httpclose enabled"); + rule.append("\n\toption httpclose"); } rule.append("\n\tstats enable\n\tstats uri ") - .append(lbCmd.lbStatsUri) - .append("\n\tstats realm Haproxy\\ Statistics\n\tstats auth ") - .append(lbCmd.lbStatsAuth); - rule.append("\n"); + .append(lbCmd.lbStatsUri) + .append("\n\tstats realm Haproxy\\ Statistics\n\tstats auth ") + .append(lbCmd.lbStatsAuth) + .append("\n"); Review Comment: ```suggestion .append(lbCmd.lbStatsUri) .append("\n\tstats realm Haproxy\\ Statistics\n\tstats auth ") .append(lbCmd.lbStatsAuth) .append("\n"); ``` ########## server/src/main/java/org/apache/cloudstack/network/ssl/CertServiceImpl.java: ########## @@ -423,19 +432,47 @@ private void validateChain(final List<Certificate> chain, final Certificate cert } - public PrivateKey parsePrivateKey(final String key) throws IOException { + public PrivateKey parsePrivateKey(final String key, String password) throws IOException, OperatorCreationException, PKCSException, NoSuchAlgorithmException, InvalidKeySpecException { Preconditions.checkArgument(StringUtils.isNotEmpty(key)); - try (final PemReader pemReader = new PemReader(new StringReader(key));) { - final PemObject pemObject = pemReader.readPemObject(); - final byte[] content = pemObject.getContent(); - final PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content); - final KeyFactory factory = KeyFactory.getInstance("RSA", "BC"); - return factory.generatePrivate(privKeySpec); - } catch (NoSuchAlgorithmException | NoSuchProviderException e) { - throw new IOException("No encryption provider available.", e); - } catch (final InvalidKeySpecException e) { - throw new IOException("Invalid Key format.", e); + PEMParser pemParser = new PEMParser(new StringReader(key)); + Object privateKeyObj = pemParser.readObject(); + if (privateKeyObj == null) { + throw new CloudRuntimeException("Cannot parse private key"); + } + PrivateKey privateKey; + if (privateKeyObj instanceof PKCS8EncryptedPrivateKeyInfo) { + if (password == null) { + throw new CloudRuntimeException("Key is encrypted by PKCS#8 but password is null"); + } + PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo)privateKeyObj; + JceOpenSSLPKCS8DecryptorProviderBuilder builder = new JceOpenSSLPKCS8DecryptorProviderBuilder(); + InputDecryptorProvider decryptor = builder.build(password.toCharArray()); + + PrivateKeyInfo privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo(decryptor); + String algorithm = privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(); + KeyFactory keyFactory = KeyFactory.getInstance(algorithm); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded()); + return keyFactory.generatePrivate(keySpec); + } else if (privateKeyObj instanceof PEMEncryptedKeyPair) { + if (password == null) { + throw new CloudRuntimeException("Key is encrypted but password is null"); + } + PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair)privateKeyObj; + privateKey = new JcaPEMKeyConverter().getKeyPair( + encryptedKeyPair.decryptKeyPair(new JcePEMDecryptorProviderBuilder().build(password.toCharArray()))).getPrivate(); Review Comment: createPEMEncryptedKey(..) ########## server/src/main/java/org/apache/cloudstack/network/ssl/CertServiceImpl.java: ########## @@ -423,19 +432,47 @@ private void validateChain(final List<Certificate> chain, final Certificate cert } - public PrivateKey parsePrivateKey(final String key) throws IOException { + public PrivateKey parsePrivateKey(final String key, String password) throws IOException, OperatorCreationException, PKCSException, NoSuchAlgorithmException, InvalidKeySpecException { Preconditions.checkArgument(StringUtils.isNotEmpty(key)); - try (final PemReader pemReader = new PemReader(new StringReader(key));) { - final PemObject pemObject = pemReader.readPemObject(); - final byte[] content = pemObject.getContent(); - final PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content); - final KeyFactory factory = KeyFactory.getInstance("RSA", "BC"); - return factory.generatePrivate(privKeySpec); - } catch (NoSuchAlgorithmException | NoSuchProviderException e) { - throw new IOException("No encryption provider available.", e); - } catch (final InvalidKeySpecException e) { - throw new IOException("Invalid Key format.", e); + PEMParser pemParser = new PEMParser(new StringReader(key)); + Object privateKeyObj = pemParser.readObject(); + if (privateKeyObj == null) { + throw new CloudRuntimeException("Cannot parse private key"); + } + PrivateKey privateKey; + if (privateKeyObj instanceof PKCS8EncryptedPrivateKeyInfo) { + if (password == null) { + throw new CloudRuntimeException("Key is encrypted by PKCS#8 but password is null"); + } + PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo)privateKeyObj; + JceOpenSSLPKCS8DecryptorProviderBuilder builder = new JceOpenSSLPKCS8DecryptorProviderBuilder(); + InputDecryptorProvider decryptor = builder.build(password.toCharArray()); + + PrivateKeyInfo privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo(decryptor); + String algorithm = privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(); + KeyFactory keyFactory = KeyFactory.getInstance(algorithm); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded()); + return keyFactory.generatePrivate(keySpec); + } else if (privateKeyObj instanceof PEMEncryptedKeyPair) { + if (password == null) { + throw new CloudRuntimeException("Key is encrypted but password is null"); + } + PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair)privateKeyObj; + privateKey = new JcaPEMKeyConverter().getKeyPair( + encryptedKeyPair.decryptKeyPair(new JcePEMDecryptorProviderBuilder().build(password.toCharArray()))).getPrivate(); + } else if (privateKeyObj instanceof PEMKeyPair) { + // Key pair + PEMKeyPair pemKeyPair = (PEMKeyPair) privateKeyObj; + privateKey = new JcaPEMKeyConverter().getKeyPair(pemKeyPair).getPrivate(); + } else if (privateKeyObj instanceof PrivateKeyInfo) { + // Private key only + PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) privateKeyObj; + privateKey = new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo); + } else { + throw new IllegalArgumentException("Unsupported PEM object: " + privateKeyObj.getClass()); } Review Comment: once we are ready for java21 we should convert to switch on type of `privateKeyObj` ########## test/integration/smoke/test_ssl_offloading.py: ########## @@ -0,0 +1,553 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import wait_until +from marvin.lib.base import (Account, + Project, + UserData, + SslCertificate, + Template, + NetworkOffering, + ServiceOffering, + VirtualMachine, + Network, + VPC, + VpcOffering, + PublicIPAddress, + LoadBalancerRule) +from marvin.lib.common import (get_domain, get_zone, get_test_template) +from nose.plugins.attrib import attr + +import os +import subprocess + + +_multiprocess_shared_ = True + +DOMAIN = "test-ssl-offloading.cloudstack.org" +CONTENT = "Test page" +FULL_CHAIN = "/tmp/full_chain.crt" + +CERT = { + "privatekey": """-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCph7jsoMCQirRn +3obuvgnnefTXRQYd9tF9k2aCVkTiiisvC39px7MGdgvDXADhD9fmR7oyXVQlfNu0 +rXjjgsVT3r4bv+DVi81YGXnuU7h10yCOZJt21i6QGHN1CS0/TAfg0UhlACCEYNRx +kB0klwUcj/jk/AKil1DoUGpvAm2gZsek/njb76/AeIfxc+Es4ZOPCVqQOHp6gI0q +t6KDMkUwv8fyzrpScygMUPVYrLmm6D0pn8yd3ihW07wGxMjND6UgOnao8t6H3LaM +Pe7eqSFzxunF9NFFjnUrKcHZZSledDM/37Kbqb/8T5f+4SwjioS1OdPCh8ApdiXq +HNUwYkALAgMBAAECggEAK5JiiQ7X7053B6s96uaVDRVfRGTNKa5iMXBNDHq3wbHZ +X4IJAVr+PE7ivxdKco3r45fT11X9ZpUsssdTJsZZiTDak69BTiFcaaRCnmqOIlpd +J7vb6TMrTIW8RvxQ0M/txm6DuNHLibqJX5a2pszZ13l5cwECfF9/v/XLJTTukCbu +6D/f3fBVFl1tM8y9saOEYLkdb4dILWY61bVSDNswgprz2EV1SFnk5jxz2FuBrM/Q ++7hINvjDcaRvcm59hRb1rkljv7S10VoNw/CFkU451csJkUe4vWZwB8lZK/XxLQG0 +HEdS1zU1XY8H8Y1RCrxjGRyiiWsBtUThhWYlPrGCoQKBgQDkP09YAlKqXhT69Kx5 +keg2i1jV2hA73zWbWXt9xp5jG5r3pl3m170DvKL93YIDnHtpTC56mlzGrzS7DSTN +p0buY9Qb3fkJxunCpPVFo0HMFkpeR77ax0v34NzSohlRLKFo5R2M1cmDfbVbnSSl +MB57FfRRMxzjrk+dJvjOeJsxjwKBgQC+JLb4B8CZjpurXYg3ySiRqFsCqkqob+kf +9dR+rWvcR6vMTEyha0hUlDvTikDepU2smYR4oPHfdcXF9lAJ7T02UmQDeizAqR68 +u9e+yS0q3tdRnPPZmXJfaDCXG1hKMqF4YA5Vs0XAjleF3zHB+vBLrnlPpShtd/Mu +sWTpxICTxQKBgQDSr/n+pE5IQwYczOO0aFGwn5pF9L9NdPHXz5aleETV+TJn7WL6 +ZiRsoaDWs7SCvtxQS2kP9RM0t5/2FeDmEMXx4aZ2fsSWGM3IxVo+iL+Aswa81n8/ +Ff5y9lb/+29hNdBcsjk/ukwEG3Lf+UNNVAie15oppgPByzJkPwgmFsAy0wKBgHDX +/TZp82WuerhSw/rHiSoYjhqg0bnw4Ju1Gy0q4q5SYqTWS0wpDT4U0wSSMjlwRQ6/ +9RxZ9/G0RXFc4tdhUkig0PY3VcPpGnLL0BhL8GBW69ZlnVpwdK4meV/UPKucLLPx +3dACmszSLSMn+LG0qVNg8mHQFJQS8eGuKcOKePw5AoGACuxtefROKdKOALh4lTi2 +VOwPZ+1jxsm6lKNccIEvbUpe3UXPgNWpJiDX8mUcob4/NBLzmV3BUVKbG7Exbo5J +LoMfp7OsztWUFwt7YAvRfS8fHdhkEsxEf3T72ADieH5ZAuXFF+K0H3r6HtWPD4ws +mTJjGP4+Bl/dFakA5FJcjHg= +-----END PRIVATE KEY-----""", + "certificate": """-----BEGIN CERTIFICATE----- +MIIFKjCCAxKgAwIBAgIUJ7BtN56KI8OuzbbM8SdtCLCB2UgwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCWFgxCzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEG +A1UECgwKQ2xvdWRTdGFjazEPMA0GA1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFj +aGUwHhcNMjUwNjIzMTMxMzA3WhcNMzUwNjIxMTMxMzA3WjBoMQswCQYDVQQGEwJY +WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMQ8wDQYDVQQKDAZBcGFjaGUxEzAR +BgNVBAsMCkNsb3VkU3RhY2sxGTAXBgNVBAMMECouY2xvdWRzdGFjay5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCph7jsoMCQirRn3obuvgnnefTX +RQYd9tF9k2aCVkTiiisvC39px7MGdgvDXADhD9fmR7oyXVQlfNu0rXjjgsVT3r4b +v+DVi81YGXnuU7h10yCOZJt21i6QGHN1CS0/TAfg0UhlACCEYNRxkB0klwUcj/jk +/AKil1DoUGpvAm2gZsek/njb76/AeIfxc+Es4ZOPCVqQOHp6gI0qt6KDMkUwv8fy +zrpScygMUPVYrLmm6D0pn8yd3ihW07wGxMjND6UgOnao8t6H3LaMPe7eqSFzxunF +9NFFjnUrKcHZZSledDM/37Kbqb/8T5f+4SwjioS1OdPCh8ApdiXqHNUwYkALAgMB +AAGjgdUwgdIwKwYDVR0RBCQwIoIQKi5jbG91ZHN0YWNrLm9yZ4IOY2xvdWRzdGFj +ay5vcmcwHQYDVR0OBBYEFCcq7jrdsqTD+Xi85DCqjYdL1gOqMIGDBgNVHSMEfDB6 +oWKkYDBeMQswCQYDVQQGEwJYWDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMw +EQYDVQQKDApDbG91ZFN0YWNrMQ8wDQYDVQQLDAZBcGFjaGUxDzANBgNVBAMMBkFw +YWNoZYIURVB9+qvRJyOnJnqmYOw467vW3vQwDQYJKoZIhvcNAQELBQADggIBACld +lEXgn/A4/kZQbLwwMxBvaoPDDaDaYVpPbOoPw7a8YkrL0rmPIc04PyX9GAqxdC+c +qaEXvmp3I+BdT13XGcBosXO8uEQ3kses9F3MhOHORPS2mJag7t4eLnNX/0CgKTlR +6yC2Gu7d3xPNJ+CKMxekdoF31StEFNAYI/La/q3D+IGsRCbrVu3xpPaw2XlXI7Ro +RU7yebVmQPSNc75bm8Ydo1cdYtz9h8PVnc+6ThhSrdS3jYScj9DrX5ZJaKuZqSlu +0ZqFXoBflme+cYB7nb9HqnIO67r9vzd2dTcErJVAk5jQqG5Y38d1tingDx1A5opU +z4BkXEbHNV6VXYUQ5VE0dXO2sNvXVJrstwMPE8d3EvbX/1gWj8kuymbskrCjySE4 +4Yztkb0dsJkVU793lz3EV75DsXvj3gevK049nPv2Grt1+rTgFNa6NJnLvKIKk/mv +fWjxbK2b/AAJ1ci6xtw/vKmIWoEu6uEMIJmhfBwuP+VnVJWJbmYXpNW/L5g21B76 +Fn8RuQa3mlm5lZrxEcJ/b6fF+2NPJwj7sh6l688VtNXoVSSyXUeV5HwqCv+YMjKn +CtwpEN/eNHMbrkJvgYwSoOzqhV/wpmNi28S7MOm66JMECHOXOhk/eX2chIEjiVna +MXhvr/Twfj2N4gNVtcgXkrk39HEYjk5+uF7SdNf4 +-----END CERTIFICATE-----""", + "certchain": """-----BEGIN CERTIFICATE----- +MIIFQzCCAysCFEVQffqr0ScjpyZ6pmDsOOu71t70MA0GCSqGSIb3DQEBCwUAMF4x +CzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxEzARBgNVBAoM +CkNsb3VkU3RhY2sxDzANBgNVBAsMBkFwYWNoZTEPMA0GA1UEAwwGQXBhY2hlMB4X +DTI1MDYxNjEwMjc1NloXDTMwMDYxNTEwMjc1NlowXjELMAkGA1UEBhMCWFgxCzAJ +BgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKQ2xvdWRTdGFjazEPMA0G +A1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFjaGUwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCLiQmSjrht15R1F+r79m/LZN5hsfQBGp+dy+yrtsWfOOur +RdXAwgbLxxsyKMQKWCQxlRI7wdhqh0L0ZBrIr9MjltYqsqLAoLmgY4eG/f6G8YGr +O/rxzfwTLbCeaIseF/OMA6Sz125HXYp1bltYK4LsuC7tihZXbeVa5pUGs3Jwgcfx +LYm4eB42Hp7Eg05uL8LbwT/1AjcwoWkTewKAWXA83zgLRDFDbl1t0IPHI4cdVvia +BNwNbG49ZCF6OgmokSarQSe4Vbems1u9T9pAySXAVjEYBqFjKWyswpdr782uNLmB +lCGm0pDeJ9/WASxbTJr7k9H6ZpnaHr54DG6ZqennWMz8w6r2pf7bp/EGZ3mZQ4s3 +5ylSP4cQt8CSSI8k2CflPGUyytUAiWlDS3qSyIuAOPKXDg7wIpcbwcu4VMeKnH0Z +x7Uu9j1UDZEZoSu6UI/VInTl47k1/ECD+AO9yBzZSv+pTQmO3/Im3CcxsTHmVd5s +Tl0CJ/jWNpo9DAMtmGvt6CBWBXGRsO2XNk7djRcq2CubiCpvODg+7CcR6CiZK73L +1aOisLiq3+ofiJSSXRRuKtJlkQ4eSPSbYWkNJcKmIhbCoYOdH/Pe3/+RHjvNc1kO +OUb+icmfzcMVAs3C5jybpazsfjDNQZXWAFx4FLDcqOVbrCwom+tMukw+hzlZnwID +AQABMA0GCSqGSIb3DQEBCwUAA4ICAQAdexoMwn+Ol1A3+NOHk9WJZX+t2Q8/9wWb +K+jSVleSfXXWsB1mC3fABVJQdCxtXCH3rpt4w7FK6aUes9VjqAHap4tt9WBq0Wqq +vvMURFHfllxEM31Z35kBOCSQY9xwpGV1rRh/zYs4h55YixomR3nXNZ9cI8xzlSCi +sMG0mv0y+yxPohKrZj3AzLYz/M11SimSoyRPIANI+cUg1nJXyQoHzVHWEp1Nj0HB +M/GW05cxsWea7f5YcAW1JQI3FOkpwb72fIZOtMDa4PO8IYWXJAeAc/chw745/MTi +Rvl2NT4RZBAcrSNbhCOzRPG/ZiG+ArQuCluZ9HHAXRBMTtlLk5DO4+XxZlyGpjwf +uKniK8dccy9uU0ho73p9SNDhXH0yb9Naj8vd9NWzCUYaaBXt/92cIyhaAHAVFxJu +o6jr2FLbnhSGF9EO/tHvF7LxZv1dnbInvlWHwoFQjwmoeB+e17lHBdPMnWnPKBZe +jA2VH/IzGCucWuWQhruummO5GT8Z6F4jBwvafBo+QARKPZgEBpx3LycXrpkYI3LT +GGOpGCxFt5tVZOEsC/jQ5rIljNSeTzWmzfNRn/yRUW97uWsrzcQIBAUtu/pQnyFQ +WCnC1ipCp1zhJsXAFUKuqEfLngXodOvC4tAOr76h11S57o5lN4506Poq2mWgAZe/ +JZr9MEn1+w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFnzCCA4egAwIBAgIUcUNMqgWoDLsvMj0YmEudj60EG5swDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCWFgxCzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEG +A1UECgwKQ2xvdWRTdGFjazEPMA0GA1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFj +aGUwIBcNMjUwNjE2MTAyNzM2WhgPMjEyNTA1MjMxMDI3MzZaMF4xCzAJBgNVBAYT +AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxEzARBgNVBAoMCkNsb3VkU3Rh +Y2sxDzANBgNVBAsMBkFwYWNoZTEPMA0GA1UEAwwGQXBhY2hlMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAwVQaePulUM523gKw168ToYp+gt05bXbu4Gg8 +uaRDKhnRAX1sEgYwkQ36Q+iTDEM9sKRma8lMNMIqkZMQdk6sIGX6BL+6wUOb7mL0 +5+I0yO9i8ooaGgNaeNvZftNIRlLsnPMGJaeom2/66XV4CsMqoZKaJ1H/I8N+bAeD +GvrBx+B4l9D3G390nQvot9JUzrJgGuLl0KDHapvhlR39cCgEfIii02uX1iy0qXlV +b+G1kLvpeC7T+lsJxondPJ69aO3lbDv/izyWw7qqBC57UhT/oKDxJmjQqklqzhgt +nM/p3YE7M0nkRi3LnRmsZBz7o1DRf+M29zypKzXVk1aJflL46AtLMmpDIzVrEB2M +q7o47rstXusYRYsBCqGTgdI1fV/CkDsZY5XkPZh2dsjZCHIS4P03OqFGsc6PQha2 ++y2AhV1pvywkDl48kPKSukHfV1RtaPZUZtcQKztwHH+aFfo9mD8z0H2HcExdXKzd +jhRhI9ZSwFj3HEN9f5P8fS3lf5+fV7EEbG4NisieBj/UivW6QiTHpLD7wRLIUt2g +XgXNF0lfJzYHbIcxQ6kfC5McU2fu6mUC+p/pNN8G0POS3S2T55tEUqLL4N0SadQy +N1TZlTd2xTn+Hb6WlG0f5m97xGcNlGHKBvntFrHvOIfkEQ9ne3MlOO1Gjlintowo +fRGf15kCAwEAAaNTMFEwHQYDVR0OBBYEFM4WEQJpN9M07Q8CHq+5owG93Dj8MB8G +A1UdIwQYMBaAFM4WEQJpN9M07Q8CHq+5owG93Dj8MA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggIBABr5RKGc/rKx4fOgyXNJR4aCJCTtPZKs4AUCCBYz +cWOjJYGNvThOPSVx51nAD8+YP2BwkOIPfhl2u/+vQSSbz4OlauXLki2DUN8E2OFe +gJfxPDzWOfAo/QOJcyHwSlnIQjiZzG2lK3eyf5IFnfQILQzDvXaUYvMBkl2hb5Q7 +44H6tRw78uuf/KsT4rY0bBFMN5DayjiyvoIDUvzCRqcb2KOi9DnZ7pXjduL7tO0j +PhlQ24B77LVUUAvydIGUzmbhGC2VvY1qE7uaYgYtgSUZ0zSjJrHjUjVLMzRouNP7 +jpbBQRAcP4FDcOFZBHogunA0hxQdm0d8u3LqDYPNS0rpfW0ddU/72nfBX4bnoDEN ++anw4wOgFuUcoEThALWZ9ESVKxXQ9Fpvd6FRW8fLLqhXAuli1BqP1c1WRxagldYe +nPGm/FGZyJ2xOak9Uigi9NAQ/vX6CEfgcJgFZmCo8EKH0d4Ut72vGUcPqiUhT2EI +AFAd6drSyoUdXXniSMWky9Vrt+qtLuAD1nhHTv8ZPdItXokoiD6ea/4xrbUZn0qY +lLMDyfY76UVF0ruTR2Q6IdSq/zSggdwgkTooOW4XZcRf5l/ZnoeVQ1QH9C85SIKH +IKZwPeGUm+EntmpuCBDmQSHLRCGEThd64iOAjqLR6arLj4TBJzBrZsGHFJbm0OcI +dwa9 +-----END CERTIFICATE-----""", + "enabledrevocationcheck": False +} + +USER_DATA="""I2Nsb3VkLWNvbmZpZwoKcnVuY21kOgogIC0gc3VkbyBhcHQtZ2V0IHVwZGF0Z +QogIC0gc3VkbyBhcHQtZ2V0IGluc3RhbGwgLXkgYXBhY2hlMgogIC0gc3Vkby +BzeXN0ZW1jdGwgZW5hYmxlIGFwYWNoZTIKICAtIHN1ZG8gc3lzdGVtY3RsIHN0 +YXJ0IGFwYWNoZTIKICAtIGVjaG8gIlRlc3QgcGFnZSIgfHN1ZG8gdGVlIC92YX +Ivd3d3L2h0bWwvaW5kZXguaHRtbAoKCg==""" + +class TestSslOffloading(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestSslOffloading, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls._cleanup = [] + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + #Create an account, network, VM and IP addresses + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls.user = cls.account.user[0] + cls.userapiclient = cls.testClient.getUserApiClient(cls.user.username, cls.domain.name) + + # Save full chain as a file + with open(FULL_CHAIN, "w", encoding="utf-8") as f: + f.write(CERT["certchain"]) + + # Register template if needed + if cls.hypervisor.lower() == 'simulator': + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor) + else: + cls.template = Template.register( + cls.apiclient, + cls.services["test_templates_cloud_init"][cls.hypervisor.lower()], + zoneid=cls.zone.id, + hypervisor=cls.hypervisor, + ) + cls.template.download(cls.apiclient) + cls._cleanup.append(cls.template) + + if cls.template == FAILED: + assert False, "get_test_template() failed to return template" + + # Create service offering + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["big"] # 512MB memory + ) + + # Create network offering + cls.services["isolated_network_offering"]["egress_policy"] = "true" + cls.network_offering = NetworkOffering.create(cls.apiclient, + cls.services["isolated_network_offering"], + conservemode=True) + cls.network_offering.update(cls.apiclient, state='Enabled') + + cls._cleanup.append(cls.network_offering) + cls._cleanup.append(cls.service_offering) + cls._cleanup.append(cls.account) Review Comment: should be right after creation, to deal with the case something goes wrong during initialisation. ########## server/src/main/java/org/apache/cloudstack/network/ssl/CertServiceImpl.java: ########## @@ -423,19 +432,47 @@ private void validateChain(final List<Certificate> chain, final Certificate cert } - public PrivateKey parsePrivateKey(final String key) throws IOException { + public PrivateKey parsePrivateKey(final String key, String password) throws IOException, OperatorCreationException, PKCSException, NoSuchAlgorithmException, InvalidKeySpecException { Preconditions.checkArgument(StringUtils.isNotEmpty(key)); - try (final PemReader pemReader = new PemReader(new StringReader(key));) { - final PemObject pemObject = pemReader.readPemObject(); - final byte[] content = pemObject.getContent(); - final PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content); - final KeyFactory factory = KeyFactory.getInstance("RSA", "BC"); - return factory.generatePrivate(privKeySpec); - } catch (NoSuchAlgorithmException | NoSuchProviderException e) { - throw new IOException("No encryption provider available.", e); - } catch (final InvalidKeySpecException e) { - throw new IOException("Invalid Key format.", e); + PEMParser pemParser = new PEMParser(new StringReader(key)); + Object privateKeyObj = pemParser.readObject(); + if (privateKeyObj == null) { + throw new CloudRuntimeException("Cannot parse private key"); + } + PrivateKey privateKey; + if (privateKeyObj instanceof PKCS8EncryptedPrivateKeyInfo) { + if (password == null) { + throw new CloudRuntimeException("Key is encrypted by PKCS#8 but password is null"); + } + PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo)privateKeyObj; + JceOpenSSLPKCS8DecryptorProviderBuilder builder = new JceOpenSSLPKCS8DecryptorProviderBuilder(); + InputDecryptorProvider decryptor = builder.build(password.toCharArray()); + + PrivateKeyInfo privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo(decryptor); + String algorithm = privateKeyInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(); + KeyFactory keyFactory = KeyFactory.getInstance(algorithm); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded()); + return keyFactory.generatePrivate(keySpec); Review Comment: method createPKCS8Key(..) ########## test/integration/smoke/test_ssl_offloading.py: ########## @@ -0,0 +1,553 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import wait_until +from marvin.lib.base import (Account, + Project, + UserData, + SslCertificate, + Template, + NetworkOffering, + ServiceOffering, + VirtualMachine, + Network, + VPC, + VpcOffering, + PublicIPAddress, + LoadBalancerRule) +from marvin.lib.common import (get_domain, get_zone, get_test_template) +from nose.plugins.attrib import attr + +import os +import subprocess + + +_multiprocess_shared_ = True + +DOMAIN = "test-ssl-offloading.cloudstack.org" +CONTENT = "Test page" +FULL_CHAIN = "/tmp/full_chain.crt" + +CERT = { + "privatekey": """-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCph7jsoMCQirRn +3obuvgnnefTXRQYd9tF9k2aCVkTiiisvC39px7MGdgvDXADhD9fmR7oyXVQlfNu0 +rXjjgsVT3r4bv+DVi81YGXnuU7h10yCOZJt21i6QGHN1CS0/TAfg0UhlACCEYNRx +kB0klwUcj/jk/AKil1DoUGpvAm2gZsek/njb76/AeIfxc+Es4ZOPCVqQOHp6gI0q +t6KDMkUwv8fyzrpScygMUPVYrLmm6D0pn8yd3ihW07wGxMjND6UgOnao8t6H3LaM +Pe7eqSFzxunF9NFFjnUrKcHZZSledDM/37Kbqb/8T5f+4SwjioS1OdPCh8ApdiXq +HNUwYkALAgMBAAECggEAK5JiiQ7X7053B6s96uaVDRVfRGTNKa5iMXBNDHq3wbHZ +X4IJAVr+PE7ivxdKco3r45fT11X9ZpUsssdTJsZZiTDak69BTiFcaaRCnmqOIlpd +J7vb6TMrTIW8RvxQ0M/txm6DuNHLibqJX5a2pszZ13l5cwECfF9/v/XLJTTukCbu +6D/f3fBVFl1tM8y9saOEYLkdb4dILWY61bVSDNswgprz2EV1SFnk5jxz2FuBrM/Q ++7hINvjDcaRvcm59hRb1rkljv7S10VoNw/CFkU451csJkUe4vWZwB8lZK/XxLQG0 +HEdS1zU1XY8H8Y1RCrxjGRyiiWsBtUThhWYlPrGCoQKBgQDkP09YAlKqXhT69Kx5 +keg2i1jV2hA73zWbWXt9xp5jG5r3pl3m170DvKL93YIDnHtpTC56mlzGrzS7DSTN +p0buY9Qb3fkJxunCpPVFo0HMFkpeR77ax0v34NzSohlRLKFo5R2M1cmDfbVbnSSl +MB57FfRRMxzjrk+dJvjOeJsxjwKBgQC+JLb4B8CZjpurXYg3ySiRqFsCqkqob+kf +9dR+rWvcR6vMTEyha0hUlDvTikDepU2smYR4oPHfdcXF9lAJ7T02UmQDeizAqR68 +u9e+yS0q3tdRnPPZmXJfaDCXG1hKMqF4YA5Vs0XAjleF3zHB+vBLrnlPpShtd/Mu +sWTpxICTxQKBgQDSr/n+pE5IQwYczOO0aFGwn5pF9L9NdPHXz5aleETV+TJn7WL6 +ZiRsoaDWs7SCvtxQS2kP9RM0t5/2FeDmEMXx4aZ2fsSWGM3IxVo+iL+Aswa81n8/ +Ff5y9lb/+29hNdBcsjk/ukwEG3Lf+UNNVAie15oppgPByzJkPwgmFsAy0wKBgHDX +/TZp82WuerhSw/rHiSoYjhqg0bnw4Ju1Gy0q4q5SYqTWS0wpDT4U0wSSMjlwRQ6/ +9RxZ9/G0RXFc4tdhUkig0PY3VcPpGnLL0BhL8GBW69ZlnVpwdK4meV/UPKucLLPx +3dACmszSLSMn+LG0qVNg8mHQFJQS8eGuKcOKePw5AoGACuxtefROKdKOALh4lTi2 +VOwPZ+1jxsm6lKNccIEvbUpe3UXPgNWpJiDX8mUcob4/NBLzmV3BUVKbG7Exbo5J +LoMfp7OsztWUFwt7YAvRfS8fHdhkEsxEf3T72ADieH5ZAuXFF+K0H3r6HtWPD4ws +mTJjGP4+Bl/dFakA5FJcjHg= +-----END PRIVATE KEY-----""", + "certificate": """-----BEGIN CERTIFICATE----- +MIIFKjCCAxKgAwIBAgIUJ7BtN56KI8OuzbbM8SdtCLCB2UgwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCWFgxCzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEG +A1UECgwKQ2xvdWRTdGFjazEPMA0GA1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFj +aGUwHhcNMjUwNjIzMTMxMzA3WhcNMzUwNjIxMTMxMzA3WjBoMQswCQYDVQQGEwJY +WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMQ8wDQYDVQQKDAZBcGFjaGUxEzAR +BgNVBAsMCkNsb3VkU3RhY2sxGTAXBgNVBAMMECouY2xvdWRzdGFjay5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCph7jsoMCQirRn3obuvgnnefTX +RQYd9tF9k2aCVkTiiisvC39px7MGdgvDXADhD9fmR7oyXVQlfNu0rXjjgsVT3r4b +v+DVi81YGXnuU7h10yCOZJt21i6QGHN1CS0/TAfg0UhlACCEYNRxkB0klwUcj/jk +/AKil1DoUGpvAm2gZsek/njb76/AeIfxc+Es4ZOPCVqQOHp6gI0qt6KDMkUwv8fy +zrpScygMUPVYrLmm6D0pn8yd3ihW07wGxMjND6UgOnao8t6H3LaMPe7eqSFzxunF +9NFFjnUrKcHZZSledDM/37Kbqb/8T5f+4SwjioS1OdPCh8ApdiXqHNUwYkALAgMB +AAGjgdUwgdIwKwYDVR0RBCQwIoIQKi5jbG91ZHN0YWNrLm9yZ4IOY2xvdWRzdGFj +ay5vcmcwHQYDVR0OBBYEFCcq7jrdsqTD+Xi85DCqjYdL1gOqMIGDBgNVHSMEfDB6 +oWKkYDBeMQswCQYDVQQGEwJYWDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMw +EQYDVQQKDApDbG91ZFN0YWNrMQ8wDQYDVQQLDAZBcGFjaGUxDzANBgNVBAMMBkFw +YWNoZYIURVB9+qvRJyOnJnqmYOw467vW3vQwDQYJKoZIhvcNAQELBQADggIBACld +lEXgn/A4/kZQbLwwMxBvaoPDDaDaYVpPbOoPw7a8YkrL0rmPIc04PyX9GAqxdC+c +qaEXvmp3I+BdT13XGcBosXO8uEQ3kses9F3MhOHORPS2mJag7t4eLnNX/0CgKTlR +6yC2Gu7d3xPNJ+CKMxekdoF31StEFNAYI/La/q3D+IGsRCbrVu3xpPaw2XlXI7Ro +RU7yebVmQPSNc75bm8Ydo1cdYtz9h8PVnc+6ThhSrdS3jYScj9DrX5ZJaKuZqSlu +0ZqFXoBflme+cYB7nb9HqnIO67r9vzd2dTcErJVAk5jQqG5Y38d1tingDx1A5opU +z4BkXEbHNV6VXYUQ5VE0dXO2sNvXVJrstwMPE8d3EvbX/1gWj8kuymbskrCjySE4 +4Yztkb0dsJkVU793lz3EV75DsXvj3gevK049nPv2Grt1+rTgFNa6NJnLvKIKk/mv +fWjxbK2b/AAJ1ci6xtw/vKmIWoEu6uEMIJmhfBwuP+VnVJWJbmYXpNW/L5g21B76 +Fn8RuQa3mlm5lZrxEcJ/b6fF+2NPJwj7sh6l688VtNXoVSSyXUeV5HwqCv+YMjKn +CtwpEN/eNHMbrkJvgYwSoOzqhV/wpmNi28S7MOm66JMECHOXOhk/eX2chIEjiVna +MXhvr/Twfj2N4gNVtcgXkrk39HEYjk5+uF7SdNf4 +-----END CERTIFICATE-----""", + "certchain": """-----BEGIN CERTIFICATE----- +MIIFQzCCAysCFEVQffqr0ScjpyZ6pmDsOOu71t70MA0GCSqGSIb3DQEBCwUAMF4x +CzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxEzARBgNVBAoM +CkNsb3VkU3RhY2sxDzANBgNVBAsMBkFwYWNoZTEPMA0GA1UEAwwGQXBhY2hlMB4X +DTI1MDYxNjEwMjc1NloXDTMwMDYxNTEwMjc1NlowXjELMAkGA1UEBhMCWFgxCzAJ +BgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKQ2xvdWRTdGFjazEPMA0G +A1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFjaGUwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCLiQmSjrht15R1F+r79m/LZN5hsfQBGp+dy+yrtsWfOOur +RdXAwgbLxxsyKMQKWCQxlRI7wdhqh0L0ZBrIr9MjltYqsqLAoLmgY4eG/f6G8YGr +O/rxzfwTLbCeaIseF/OMA6Sz125HXYp1bltYK4LsuC7tihZXbeVa5pUGs3Jwgcfx +LYm4eB42Hp7Eg05uL8LbwT/1AjcwoWkTewKAWXA83zgLRDFDbl1t0IPHI4cdVvia +BNwNbG49ZCF6OgmokSarQSe4Vbems1u9T9pAySXAVjEYBqFjKWyswpdr782uNLmB +lCGm0pDeJ9/WASxbTJr7k9H6ZpnaHr54DG6ZqennWMz8w6r2pf7bp/EGZ3mZQ4s3 +5ylSP4cQt8CSSI8k2CflPGUyytUAiWlDS3qSyIuAOPKXDg7wIpcbwcu4VMeKnH0Z +x7Uu9j1UDZEZoSu6UI/VInTl47k1/ECD+AO9yBzZSv+pTQmO3/Im3CcxsTHmVd5s +Tl0CJ/jWNpo9DAMtmGvt6CBWBXGRsO2XNk7djRcq2CubiCpvODg+7CcR6CiZK73L +1aOisLiq3+ofiJSSXRRuKtJlkQ4eSPSbYWkNJcKmIhbCoYOdH/Pe3/+RHjvNc1kO +OUb+icmfzcMVAs3C5jybpazsfjDNQZXWAFx4FLDcqOVbrCwom+tMukw+hzlZnwID +AQABMA0GCSqGSIb3DQEBCwUAA4ICAQAdexoMwn+Ol1A3+NOHk9WJZX+t2Q8/9wWb +K+jSVleSfXXWsB1mC3fABVJQdCxtXCH3rpt4w7FK6aUes9VjqAHap4tt9WBq0Wqq +vvMURFHfllxEM31Z35kBOCSQY9xwpGV1rRh/zYs4h55YixomR3nXNZ9cI8xzlSCi +sMG0mv0y+yxPohKrZj3AzLYz/M11SimSoyRPIANI+cUg1nJXyQoHzVHWEp1Nj0HB +M/GW05cxsWea7f5YcAW1JQI3FOkpwb72fIZOtMDa4PO8IYWXJAeAc/chw745/MTi +Rvl2NT4RZBAcrSNbhCOzRPG/ZiG+ArQuCluZ9HHAXRBMTtlLk5DO4+XxZlyGpjwf +uKniK8dccy9uU0ho73p9SNDhXH0yb9Naj8vd9NWzCUYaaBXt/92cIyhaAHAVFxJu +o6jr2FLbnhSGF9EO/tHvF7LxZv1dnbInvlWHwoFQjwmoeB+e17lHBdPMnWnPKBZe +jA2VH/IzGCucWuWQhruummO5GT8Z6F4jBwvafBo+QARKPZgEBpx3LycXrpkYI3LT +GGOpGCxFt5tVZOEsC/jQ5rIljNSeTzWmzfNRn/yRUW97uWsrzcQIBAUtu/pQnyFQ +WCnC1ipCp1zhJsXAFUKuqEfLngXodOvC4tAOr76h11S57o5lN4506Poq2mWgAZe/ +JZr9MEn1+w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFnzCCA4egAwIBAgIUcUNMqgWoDLsvMj0YmEudj60EG5swDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCWFgxCzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEG +A1UECgwKQ2xvdWRTdGFjazEPMA0GA1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFj +aGUwIBcNMjUwNjE2MTAyNzM2WhgPMjEyNTA1MjMxMDI3MzZaMF4xCzAJBgNVBAYT +AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxEzARBgNVBAoMCkNsb3VkU3Rh +Y2sxDzANBgNVBAsMBkFwYWNoZTEPMA0GA1UEAwwGQXBhY2hlMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAwVQaePulUM523gKw168ToYp+gt05bXbu4Gg8 +uaRDKhnRAX1sEgYwkQ36Q+iTDEM9sKRma8lMNMIqkZMQdk6sIGX6BL+6wUOb7mL0 +5+I0yO9i8ooaGgNaeNvZftNIRlLsnPMGJaeom2/66XV4CsMqoZKaJ1H/I8N+bAeD +GvrBx+B4l9D3G390nQvot9JUzrJgGuLl0KDHapvhlR39cCgEfIii02uX1iy0qXlV +b+G1kLvpeC7T+lsJxondPJ69aO3lbDv/izyWw7qqBC57UhT/oKDxJmjQqklqzhgt +nM/p3YE7M0nkRi3LnRmsZBz7o1DRf+M29zypKzXVk1aJflL46AtLMmpDIzVrEB2M +q7o47rstXusYRYsBCqGTgdI1fV/CkDsZY5XkPZh2dsjZCHIS4P03OqFGsc6PQha2 ++y2AhV1pvywkDl48kPKSukHfV1RtaPZUZtcQKztwHH+aFfo9mD8z0H2HcExdXKzd +jhRhI9ZSwFj3HEN9f5P8fS3lf5+fV7EEbG4NisieBj/UivW6QiTHpLD7wRLIUt2g +XgXNF0lfJzYHbIcxQ6kfC5McU2fu6mUC+p/pNN8G0POS3S2T55tEUqLL4N0SadQy +N1TZlTd2xTn+Hb6WlG0f5m97xGcNlGHKBvntFrHvOIfkEQ9ne3MlOO1Gjlintowo +fRGf15kCAwEAAaNTMFEwHQYDVR0OBBYEFM4WEQJpN9M07Q8CHq+5owG93Dj8MB8G +A1UdIwQYMBaAFM4WEQJpN9M07Q8CHq+5owG93Dj8MA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggIBABr5RKGc/rKx4fOgyXNJR4aCJCTtPZKs4AUCCBYz +cWOjJYGNvThOPSVx51nAD8+YP2BwkOIPfhl2u/+vQSSbz4OlauXLki2DUN8E2OFe +gJfxPDzWOfAo/QOJcyHwSlnIQjiZzG2lK3eyf5IFnfQILQzDvXaUYvMBkl2hb5Q7 +44H6tRw78uuf/KsT4rY0bBFMN5DayjiyvoIDUvzCRqcb2KOi9DnZ7pXjduL7tO0j +PhlQ24B77LVUUAvydIGUzmbhGC2VvY1qE7uaYgYtgSUZ0zSjJrHjUjVLMzRouNP7 +jpbBQRAcP4FDcOFZBHogunA0hxQdm0d8u3LqDYPNS0rpfW0ddU/72nfBX4bnoDEN ++anw4wOgFuUcoEThALWZ9ESVKxXQ9Fpvd6FRW8fLLqhXAuli1BqP1c1WRxagldYe +nPGm/FGZyJ2xOak9Uigi9NAQ/vX6CEfgcJgFZmCo8EKH0d4Ut72vGUcPqiUhT2EI +AFAd6drSyoUdXXniSMWky9Vrt+qtLuAD1nhHTv8ZPdItXokoiD6ea/4xrbUZn0qY +lLMDyfY76UVF0ruTR2Q6IdSq/zSggdwgkTooOW4XZcRf5l/ZnoeVQ1QH9C85SIKH +IKZwPeGUm+EntmpuCBDmQSHLRCGEThd64iOAjqLR6arLj4TBJzBrZsGHFJbm0OcI +dwa9 +-----END CERTIFICATE-----""", + "enabledrevocationcheck": False +} + +USER_DATA="""I2Nsb3VkLWNvbmZpZwoKcnVuY21kOgogIC0gc3VkbyBhcHQtZ2V0IHVwZGF0Z +QogIC0gc3VkbyBhcHQtZ2V0IGluc3RhbGwgLXkgYXBhY2hlMgogIC0gc3Vkby +BzeXN0ZW1jdGwgZW5hYmxlIGFwYWNoZTIKICAtIHN1ZG8gc3lzdGVtY3RsIHN0 +YXJ0IGFwYWNoZTIKICAtIGVjaG8gIlRlc3QgcGFnZSIgfHN1ZG8gdGVlIC92YX +Ivd3d3L2h0bWwvaW5kZXguaHRtbAoKCg==""" + +class TestSslOffloading(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestSslOffloading, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls._cleanup = [] + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + #Create an account, network, VM and IP addresses + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls.user = cls.account.user[0] + cls.userapiclient = cls.testClient.getUserApiClient(cls.user.username, cls.domain.name) + + # Save full chain as a file + with open(FULL_CHAIN, "w", encoding="utf-8") as f: + f.write(CERT["certchain"]) + + # Register template if needed + if cls.hypervisor.lower() == 'simulator': + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor) + else: + cls.template = Template.register( + cls.apiclient, + cls.services["test_templates_cloud_init"][cls.hypervisor.lower()], + zoneid=cls.zone.id, + hypervisor=cls.hypervisor, + ) + cls.template.download(cls.apiclient) + cls._cleanup.append(cls.template) + + if cls.template == FAILED: + assert False, "get_test_template() failed to return template" + + # Create service offering + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["big"] # 512MB memory + ) + + # Create network offering + cls.services["isolated_network_offering"]["egress_policy"] = "true" + cls.network_offering = NetworkOffering.create(cls.apiclient, + cls.services["isolated_network_offering"], + conservemode=True) + cls.network_offering.update(cls.apiclient, state='Enabled') + + cls._cleanup.append(cls.network_offering) + cls._cleanup.append(cls.service_offering) + cls._cleanup.append(cls.account) + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + + def tearDown(self): + super(TestSslOffloading, self).tearDown() + + @classmethod + def tearDownClass(cls): + super(TestSslOffloading, cls).tearDownClass() + if os.path.exists(FULL_CHAIN): + os.remove(FULL_CHAIN) + + def wait_for_service_ready(self, command, expected, retries=60): + output = None + def check_output(): + try: + output = subprocess.check_output(command, shell=True).strip().decode('utf-8') + except Exception as e: + print("Failed to get output of command %s: %s" % (command, e)) + if expected is None: + print("But it is expected") + return True, None + return False, None + print("Output of command %s: \n %s" %(command, output)) + if expected is None: + print("But it is expected to be None") + return False, None + return (expected in output), None + + res = wait_until(10, retries, check_output) + if not res: + self.fail("Failed to wait for http server to show content '%s'. The output is '%s'" % (expected, output)) + return res + + @attr(tags = ["advanced", "advancedns", "smoke"], required_hardware="true") + def test_01_ssl_offloading_isolated_network(self): + """Test to create Load balancing rule with SSL offloading""" + + # Validate: + # 1. Create isolated network and vm instance + # 2. create LB with port 80 -> 80, verify the website (should get expected content) + # 3. create LB with port 443 -> 80, verify the website (should not work) + # 4. add cert to LB with port 443 + # 5. verify the website (should get expected content) + # 6. remove cert from LB with port 443 + # 7. delete SSL certificate + + # Register Userdata + self.userdata = UserData.register(self.apiclient, + name="test-userdata", + userdata=USER_DATA, + account=self.account.name, + domainid=self.account.domainid + ) + + # Upload SSL Certificate + self.sslcert = SslCertificate.create(self.apiclient, + CERT, + name="test-ssl-certificate", + account=self.account.name, + domainid=self.account.domainid) + + # 1. Create network + self.network = Network.create(self.apiclient, + zoneid=self.zone.id, + services=self.services["network"], + domainid=self.domain.id, + account=self.account.name, + networkofferingid=self.network_offering.id) + self.cleanup.append(self.network) + + self.services["virtual_machine"]["networkids"] = [str(self.network.id)] + + # Create vm instance + self.vm_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + userdataid=self.userdata.userdata.id, + serviceofferingid=self.service_offering.id + ) + self.cleanup.append(self.vm_1) + + self.public_ip = PublicIPAddress.create( + self.apiclient, + self.account.name, + self.zone.id, + self.account.domainid, + self.services["virtual_machine"], + self.network.id) + + # 2. create LB with port 80 -> 80, verify the website (should get expected content). + # firewall is open by default + lb_http = { + "name": "http", + "alg": "roundrobin", + "privateport": 80, + "publicport": 80, + "protocol": "tcp" + } + lb_rule_http = LoadBalancerRule.create( + self.apiclient, + lb_http, + self.public_ip.ipaddress.id, + accountid=self.account.name, + domainid=self.domain.id, + networkid=self.network.id + ) + lb_rule_http.assign(self.apiclient, [self.vm_1]) + command = "curl -L --connect-timeout 3 http://%s/" % self.public_ip.ipaddress.ipaddress + # wait 10 minutes until the webpage is available. it returns "503 Service Unavailable" if not available + self.wait_for_service_ready(command, CONTENT, 60) + + # 3. create LB with port 443 -> 80, verify the website (should not work) + # firewall is open by default + lb_https = { + "name": "https", + "alg": "roundrobin", + "privateport": 80, + "publicport": 443, + "protocol": "ssl" + } + lb_rule_https = LoadBalancerRule.create( + self.apiclient, + lb_https, + self.public_ip.ipaddress.id, + accountid=self.account.name, + domainid=self.domain.id, + networkid=self.network.id + ) + lb_rule_https.assign(self.apiclient, [self.vm_1]) + + command = "curl -L --connect-timeout 3 -k --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + # 4. add cert to LB with port 443 + lb_rule_https.assignCert(self.apiclient, self.sslcert.id) + + # 5. verify the website (should get expected content) + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, "SSL certificate problem", 1) + + command = "curl -L --connect-timeout 3 -k --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + command = "curl -L --connect-timeout 3 --cacert %s --resolve %s:443:%s https://%s/" % (FULL_CHAIN, DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + # 6. remove cert from LB with port 443 + lb_rule_https.removeCert(self.apiclient) + + # 7. delete SSL certificate + self.sslcert.delete(self.apiclient) + + @attr(tags = ["advanced", "advancedns", "smoke"], required_hardware="true") + def test_02_ssl_offloading_project_vpc(self): + """Test to create Load balancing rule with SSL offloading in VPC in user project""" + + # Validate: + # 1. Create VPC, VPC tier and vm instance + # 2. create LB with port 80 -> 80, verify the website (should get expected content) + # 3. create LB with port 443 -> 80, verify the website (should not work) + # 4. add cert to LB with port 443 + # 5. verify the website (should get expected content) + # 6. remove cert from LB with port 443 + # 7. delete SSL certificate + + # Create project by user + self.project = Project.create( + self.userapiclient, + self.services["project"] + ) + self.cleanup.append(self.project) + + # Register Userdata by user + self.userdata = UserData.register(self.userapiclient, + name="test-user-userdata", + userdata=USER_DATA, + projectid=self.project.id + ) + + # Upload SSL Certificate by user + self.sslcert = SslCertificate.create(self.userapiclient, + CERT, + name="test-user-ssl-certificate", + projectid=self.project.id + ) + + # 1. Create VPC and VPC tier + vpcOffering = VpcOffering.list(self.userapiclient, name="Default VPC offering") + self.assertTrue(vpcOffering is not None and len( + vpcOffering) > 0, "No VPC offerings found") + + self.vpc = VPC.create( + apiclient=self.userapiclient, + services=self.services["vpc_vpn"]["vpc"], + vpcofferingid=vpcOffering[0].id, + zoneid=self.zone.id, + projectid=self.project.id + ) + self.cleanup.append(self.vpc) + + networkOffering = NetworkOffering.list( + self.userapiclient, name="DefaultIsolatedNetworkOfferingForVpcNetworks") + self.assertTrue(networkOffering is not None and len( + networkOffering) > 0, "No VPC based network offering") + + self.network = Network.create( + apiclient=self.userapiclient, + services=self.services["vpc_vpn"]["network_1"], + networkofferingid=networkOffering[0].id, + zoneid=self.zone.id, + vpcid=self.vpc.id, + projectid=self.project.id + ) + self.cleanup.append(self.network) + + self.services["virtual_machine"]["networkids"] = [str(self.network.id)] + + # Create vm instance + self.vm_2 = VirtualMachine.create( + self.userapiclient, + self.services["virtual_machine"], + templateid=self.template.id, + userdataid=self.userdata.userdata.id, + serviceofferingid=self.service_offering.id, + projectid=self.project.id + ) + self.cleanup.append(self.vm_2) + + self.public_ip = PublicIPAddress.create( + self.userapiclient, + zoneid=self.zone.id, + services=self.services["virtual_machine"], + networkid=self.network.id, + vpcid=self.vpc.id, + projectid=self.project.id + ) + + # 2. create LB with port 80 -> 80, verify the website (should get expected content). + # firewall is open by default + lb_http = { + "name": "http", + "alg": "roundrobin", + "privateport": 80, + "publicport": 80, + "protocol": "tcp" + } + lb_rule_http = LoadBalancerRule.create( + self.userapiclient, + lb_http, + self.public_ip.ipaddress.id, + networkid=self.network.id, + projectid=self.project.id + ) + lb_rule_http.assign(self.userapiclient, [self.vm_2]) + command = "curl -L --connect-timeout 3 http://%s/" % self.public_ip.ipaddress.ipaddress + # wait 10 minutes until the webpage is available. it returns "503 Service Unavailable" if not available + self.wait_for_service_ready(command, CONTENT, 60) + + # 3. create LB with port 443 -> 80, verify the website (should not work) + # firewall is open by default + lb_https = { + "name": "https", + "alg": "roundrobin", + "privateport": 80, + "publicport": 443, + "protocol": "ssl" + } + lb_rule_https = LoadBalancerRule.create( + self.userapiclient, + lb_https, + self.public_ip.ipaddress.id, + networkid=self.network.id, + projectid=self.project.id + ) + lb_rule_https.assign(self.userapiclient, [self.vm_2]) + + command = "curl -L --connect-timeout 3 -k --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + # 4. add cert to LB with port 443 + lb_rule_https.assignCert(self.userapiclient, self.sslcert.id) + + # 5. verify the website (should get expected content) + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, "SSL certificate problem", 1) + + command = "curl -L --connect-timeout 3 -k --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + command = "curl -L --connect-timeout 3 --cacert %s --resolve %s:443:%s https://%s/" % (FULL_CHAIN, DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + # 6. remove cert from LB with port 443 + lb_rule_https.removeCert(self.userapiclient) + + # 7. delete SSL certificate + self.sslcert.delete(self.userapiclient) Review Comment: same question as on the previous test.. ########## test/integration/smoke/test_ssl_offloading.py: ########## @@ -0,0 +1,553 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import wait_until +from marvin.lib.base import (Account, + Project, + UserData, + SslCertificate, + Template, + NetworkOffering, + ServiceOffering, + VirtualMachine, + Network, + VPC, + VpcOffering, + PublicIPAddress, + LoadBalancerRule) +from marvin.lib.common import (get_domain, get_zone, get_test_template) +from nose.plugins.attrib import attr + +import os +import subprocess + + +_multiprocess_shared_ = True + +DOMAIN = "test-ssl-offloading.cloudstack.org" +CONTENT = "Test page" +FULL_CHAIN = "/tmp/full_chain.crt" + +CERT = { + "privatekey": """-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCph7jsoMCQirRn +3obuvgnnefTXRQYd9tF9k2aCVkTiiisvC39px7MGdgvDXADhD9fmR7oyXVQlfNu0 +rXjjgsVT3r4bv+DVi81YGXnuU7h10yCOZJt21i6QGHN1CS0/TAfg0UhlACCEYNRx +kB0klwUcj/jk/AKil1DoUGpvAm2gZsek/njb76/AeIfxc+Es4ZOPCVqQOHp6gI0q +t6KDMkUwv8fyzrpScygMUPVYrLmm6D0pn8yd3ihW07wGxMjND6UgOnao8t6H3LaM +Pe7eqSFzxunF9NFFjnUrKcHZZSledDM/37Kbqb/8T5f+4SwjioS1OdPCh8ApdiXq +HNUwYkALAgMBAAECggEAK5JiiQ7X7053B6s96uaVDRVfRGTNKa5iMXBNDHq3wbHZ +X4IJAVr+PE7ivxdKco3r45fT11X9ZpUsssdTJsZZiTDak69BTiFcaaRCnmqOIlpd +J7vb6TMrTIW8RvxQ0M/txm6DuNHLibqJX5a2pszZ13l5cwECfF9/v/XLJTTukCbu +6D/f3fBVFl1tM8y9saOEYLkdb4dILWY61bVSDNswgprz2EV1SFnk5jxz2FuBrM/Q ++7hINvjDcaRvcm59hRb1rkljv7S10VoNw/CFkU451csJkUe4vWZwB8lZK/XxLQG0 +HEdS1zU1XY8H8Y1RCrxjGRyiiWsBtUThhWYlPrGCoQKBgQDkP09YAlKqXhT69Kx5 +keg2i1jV2hA73zWbWXt9xp5jG5r3pl3m170DvKL93YIDnHtpTC56mlzGrzS7DSTN +p0buY9Qb3fkJxunCpPVFo0HMFkpeR77ax0v34NzSohlRLKFo5R2M1cmDfbVbnSSl +MB57FfRRMxzjrk+dJvjOeJsxjwKBgQC+JLb4B8CZjpurXYg3ySiRqFsCqkqob+kf +9dR+rWvcR6vMTEyha0hUlDvTikDepU2smYR4oPHfdcXF9lAJ7T02UmQDeizAqR68 +u9e+yS0q3tdRnPPZmXJfaDCXG1hKMqF4YA5Vs0XAjleF3zHB+vBLrnlPpShtd/Mu +sWTpxICTxQKBgQDSr/n+pE5IQwYczOO0aFGwn5pF9L9NdPHXz5aleETV+TJn7WL6 +ZiRsoaDWs7SCvtxQS2kP9RM0t5/2FeDmEMXx4aZ2fsSWGM3IxVo+iL+Aswa81n8/ +Ff5y9lb/+29hNdBcsjk/ukwEG3Lf+UNNVAie15oppgPByzJkPwgmFsAy0wKBgHDX +/TZp82WuerhSw/rHiSoYjhqg0bnw4Ju1Gy0q4q5SYqTWS0wpDT4U0wSSMjlwRQ6/ +9RxZ9/G0RXFc4tdhUkig0PY3VcPpGnLL0BhL8GBW69ZlnVpwdK4meV/UPKucLLPx +3dACmszSLSMn+LG0qVNg8mHQFJQS8eGuKcOKePw5AoGACuxtefROKdKOALh4lTi2 +VOwPZ+1jxsm6lKNccIEvbUpe3UXPgNWpJiDX8mUcob4/NBLzmV3BUVKbG7Exbo5J +LoMfp7OsztWUFwt7YAvRfS8fHdhkEsxEf3T72ADieH5ZAuXFF+K0H3r6HtWPD4ws +mTJjGP4+Bl/dFakA5FJcjHg= +-----END PRIVATE KEY-----""", + "certificate": """-----BEGIN CERTIFICATE----- +MIIFKjCCAxKgAwIBAgIUJ7BtN56KI8OuzbbM8SdtCLCB2UgwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCWFgxCzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEG +A1UECgwKQ2xvdWRTdGFjazEPMA0GA1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFj +aGUwHhcNMjUwNjIzMTMxMzA3WhcNMzUwNjIxMTMxMzA3WjBoMQswCQYDVQQGEwJY +WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMQ8wDQYDVQQKDAZBcGFjaGUxEzAR +BgNVBAsMCkNsb3VkU3RhY2sxGTAXBgNVBAMMECouY2xvdWRzdGFjay5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCph7jsoMCQirRn3obuvgnnefTX +RQYd9tF9k2aCVkTiiisvC39px7MGdgvDXADhD9fmR7oyXVQlfNu0rXjjgsVT3r4b +v+DVi81YGXnuU7h10yCOZJt21i6QGHN1CS0/TAfg0UhlACCEYNRxkB0klwUcj/jk +/AKil1DoUGpvAm2gZsek/njb76/AeIfxc+Es4ZOPCVqQOHp6gI0qt6KDMkUwv8fy +zrpScygMUPVYrLmm6D0pn8yd3ihW07wGxMjND6UgOnao8t6H3LaMPe7eqSFzxunF +9NFFjnUrKcHZZSledDM/37Kbqb/8T5f+4SwjioS1OdPCh8ApdiXqHNUwYkALAgMB +AAGjgdUwgdIwKwYDVR0RBCQwIoIQKi5jbG91ZHN0YWNrLm9yZ4IOY2xvdWRzdGFj +ay5vcmcwHQYDVR0OBBYEFCcq7jrdsqTD+Xi85DCqjYdL1gOqMIGDBgNVHSMEfDB6 +oWKkYDBeMQswCQYDVQQGEwJYWDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMw +EQYDVQQKDApDbG91ZFN0YWNrMQ8wDQYDVQQLDAZBcGFjaGUxDzANBgNVBAMMBkFw +YWNoZYIURVB9+qvRJyOnJnqmYOw467vW3vQwDQYJKoZIhvcNAQELBQADggIBACld +lEXgn/A4/kZQbLwwMxBvaoPDDaDaYVpPbOoPw7a8YkrL0rmPIc04PyX9GAqxdC+c +qaEXvmp3I+BdT13XGcBosXO8uEQ3kses9F3MhOHORPS2mJag7t4eLnNX/0CgKTlR +6yC2Gu7d3xPNJ+CKMxekdoF31StEFNAYI/La/q3D+IGsRCbrVu3xpPaw2XlXI7Ro +RU7yebVmQPSNc75bm8Ydo1cdYtz9h8PVnc+6ThhSrdS3jYScj9DrX5ZJaKuZqSlu +0ZqFXoBflme+cYB7nb9HqnIO67r9vzd2dTcErJVAk5jQqG5Y38d1tingDx1A5opU +z4BkXEbHNV6VXYUQ5VE0dXO2sNvXVJrstwMPE8d3EvbX/1gWj8kuymbskrCjySE4 +4Yztkb0dsJkVU793lz3EV75DsXvj3gevK049nPv2Grt1+rTgFNa6NJnLvKIKk/mv +fWjxbK2b/AAJ1ci6xtw/vKmIWoEu6uEMIJmhfBwuP+VnVJWJbmYXpNW/L5g21B76 +Fn8RuQa3mlm5lZrxEcJ/b6fF+2NPJwj7sh6l688VtNXoVSSyXUeV5HwqCv+YMjKn +CtwpEN/eNHMbrkJvgYwSoOzqhV/wpmNi28S7MOm66JMECHOXOhk/eX2chIEjiVna +MXhvr/Twfj2N4gNVtcgXkrk39HEYjk5+uF7SdNf4 +-----END CERTIFICATE-----""", + "certchain": """-----BEGIN CERTIFICATE----- +MIIFQzCCAysCFEVQffqr0ScjpyZ6pmDsOOu71t70MA0GCSqGSIb3DQEBCwUAMF4x +CzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxEzARBgNVBAoM +CkNsb3VkU3RhY2sxDzANBgNVBAsMBkFwYWNoZTEPMA0GA1UEAwwGQXBhY2hlMB4X +DTI1MDYxNjEwMjc1NloXDTMwMDYxNTEwMjc1NlowXjELMAkGA1UEBhMCWFgxCzAJ +BgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKQ2xvdWRTdGFjazEPMA0G +A1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFjaGUwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCLiQmSjrht15R1F+r79m/LZN5hsfQBGp+dy+yrtsWfOOur +RdXAwgbLxxsyKMQKWCQxlRI7wdhqh0L0ZBrIr9MjltYqsqLAoLmgY4eG/f6G8YGr +O/rxzfwTLbCeaIseF/OMA6Sz125HXYp1bltYK4LsuC7tihZXbeVa5pUGs3Jwgcfx +LYm4eB42Hp7Eg05uL8LbwT/1AjcwoWkTewKAWXA83zgLRDFDbl1t0IPHI4cdVvia +BNwNbG49ZCF6OgmokSarQSe4Vbems1u9T9pAySXAVjEYBqFjKWyswpdr782uNLmB +lCGm0pDeJ9/WASxbTJr7k9H6ZpnaHr54DG6ZqennWMz8w6r2pf7bp/EGZ3mZQ4s3 +5ylSP4cQt8CSSI8k2CflPGUyytUAiWlDS3qSyIuAOPKXDg7wIpcbwcu4VMeKnH0Z +x7Uu9j1UDZEZoSu6UI/VInTl47k1/ECD+AO9yBzZSv+pTQmO3/Im3CcxsTHmVd5s +Tl0CJ/jWNpo9DAMtmGvt6CBWBXGRsO2XNk7djRcq2CubiCpvODg+7CcR6CiZK73L +1aOisLiq3+ofiJSSXRRuKtJlkQ4eSPSbYWkNJcKmIhbCoYOdH/Pe3/+RHjvNc1kO +OUb+icmfzcMVAs3C5jybpazsfjDNQZXWAFx4FLDcqOVbrCwom+tMukw+hzlZnwID +AQABMA0GCSqGSIb3DQEBCwUAA4ICAQAdexoMwn+Ol1A3+NOHk9WJZX+t2Q8/9wWb +K+jSVleSfXXWsB1mC3fABVJQdCxtXCH3rpt4w7FK6aUes9VjqAHap4tt9WBq0Wqq +vvMURFHfllxEM31Z35kBOCSQY9xwpGV1rRh/zYs4h55YixomR3nXNZ9cI8xzlSCi +sMG0mv0y+yxPohKrZj3AzLYz/M11SimSoyRPIANI+cUg1nJXyQoHzVHWEp1Nj0HB +M/GW05cxsWea7f5YcAW1JQI3FOkpwb72fIZOtMDa4PO8IYWXJAeAc/chw745/MTi +Rvl2NT4RZBAcrSNbhCOzRPG/ZiG+ArQuCluZ9HHAXRBMTtlLk5DO4+XxZlyGpjwf +uKniK8dccy9uU0ho73p9SNDhXH0yb9Naj8vd9NWzCUYaaBXt/92cIyhaAHAVFxJu +o6jr2FLbnhSGF9EO/tHvF7LxZv1dnbInvlWHwoFQjwmoeB+e17lHBdPMnWnPKBZe +jA2VH/IzGCucWuWQhruummO5GT8Z6F4jBwvafBo+QARKPZgEBpx3LycXrpkYI3LT +GGOpGCxFt5tVZOEsC/jQ5rIljNSeTzWmzfNRn/yRUW97uWsrzcQIBAUtu/pQnyFQ +WCnC1ipCp1zhJsXAFUKuqEfLngXodOvC4tAOr76h11S57o5lN4506Poq2mWgAZe/ +JZr9MEn1+w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFnzCCA4egAwIBAgIUcUNMqgWoDLsvMj0YmEudj60EG5swDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCWFgxCzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEG +A1UECgwKQ2xvdWRTdGFjazEPMA0GA1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFj +aGUwIBcNMjUwNjE2MTAyNzM2WhgPMjEyNTA1MjMxMDI3MzZaMF4xCzAJBgNVBAYT +AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxEzARBgNVBAoMCkNsb3VkU3Rh +Y2sxDzANBgNVBAsMBkFwYWNoZTEPMA0GA1UEAwwGQXBhY2hlMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAwVQaePulUM523gKw168ToYp+gt05bXbu4Gg8 +uaRDKhnRAX1sEgYwkQ36Q+iTDEM9sKRma8lMNMIqkZMQdk6sIGX6BL+6wUOb7mL0 +5+I0yO9i8ooaGgNaeNvZftNIRlLsnPMGJaeom2/66XV4CsMqoZKaJ1H/I8N+bAeD +GvrBx+B4l9D3G390nQvot9JUzrJgGuLl0KDHapvhlR39cCgEfIii02uX1iy0qXlV +b+G1kLvpeC7T+lsJxondPJ69aO3lbDv/izyWw7qqBC57UhT/oKDxJmjQqklqzhgt +nM/p3YE7M0nkRi3LnRmsZBz7o1DRf+M29zypKzXVk1aJflL46AtLMmpDIzVrEB2M +q7o47rstXusYRYsBCqGTgdI1fV/CkDsZY5XkPZh2dsjZCHIS4P03OqFGsc6PQha2 ++y2AhV1pvywkDl48kPKSukHfV1RtaPZUZtcQKztwHH+aFfo9mD8z0H2HcExdXKzd +jhRhI9ZSwFj3HEN9f5P8fS3lf5+fV7EEbG4NisieBj/UivW6QiTHpLD7wRLIUt2g +XgXNF0lfJzYHbIcxQ6kfC5McU2fu6mUC+p/pNN8G0POS3S2T55tEUqLL4N0SadQy +N1TZlTd2xTn+Hb6WlG0f5m97xGcNlGHKBvntFrHvOIfkEQ9ne3MlOO1Gjlintowo +fRGf15kCAwEAAaNTMFEwHQYDVR0OBBYEFM4WEQJpN9M07Q8CHq+5owG93Dj8MB8G +A1UdIwQYMBaAFM4WEQJpN9M07Q8CHq+5owG93Dj8MA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggIBABr5RKGc/rKx4fOgyXNJR4aCJCTtPZKs4AUCCBYz +cWOjJYGNvThOPSVx51nAD8+YP2BwkOIPfhl2u/+vQSSbz4OlauXLki2DUN8E2OFe +gJfxPDzWOfAo/QOJcyHwSlnIQjiZzG2lK3eyf5IFnfQILQzDvXaUYvMBkl2hb5Q7 +44H6tRw78uuf/KsT4rY0bBFMN5DayjiyvoIDUvzCRqcb2KOi9DnZ7pXjduL7tO0j +PhlQ24B77LVUUAvydIGUzmbhGC2VvY1qE7uaYgYtgSUZ0zSjJrHjUjVLMzRouNP7 +jpbBQRAcP4FDcOFZBHogunA0hxQdm0d8u3LqDYPNS0rpfW0ddU/72nfBX4bnoDEN ++anw4wOgFuUcoEThALWZ9ESVKxXQ9Fpvd6FRW8fLLqhXAuli1BqP1c1WRxagldYe +nPGm/FGZyJ2xOak9Uigi9NAQ/vX6CEfgcJgFZmCo8EKH0d4Ut72vGUcPqiUhT2EI +AFAd6drSyoUdXXniSMWky9Vrt+qtLuAD1nhHTv8ZPdItXokoiD6ea/4xrbUZn0qY +lLMDyfY76UVF0ruTR2Q6IdSq/zSggdwgkTooOW4XZcRf5l/ZnoeVQ1QH9C85SIKH +IKZwPeGUm+EntmpuCBDmQSHLRCGEThd64iOAjqLR6arLj4TBJzBrZsGHFJbm0OcI +dwa9 +-----END CERTIFICATE-----""", + "enabledrevocationcheck": False +} + +USER_DATA="""I2Nsb3VkLWNvbmZpZwoKcnVuY21kOgogIC0gc3VkbyBhcHQtZ2V0IHVwZGF0Z +QogIC0gc3VkbyBhcHQtZ2V0IGluc3RhbGwgLXkgYXBhY2hlMgogIC0gc3Vkby +BzeXN0ZW1jdGwgZW5hYmxlIGFwYWNoZTIKICAtIHN1ZG8gc3lzdGVtY3RsIHN0 +YXJ0IGFwYWNoZTIKICAtIGVjaG8gIlRlc3QgcGFnZSIgfHN1ZG8gdGVlIC92YX +Ivd3d3L2h0bWwvaW5kZXguaHRtbAoKCg==""" + +class TestSslOffloading(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestSslOffloading, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls._cleanup = [] + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + #Create an account, network, VM and IP addresses + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls.user = cls.account.user[0] + cls.userapiclient = cls.testClient.getUserApiClient(cls.user.username, cls.domain.name) + + # Save full chain as a file + with open(FULL_CHAIN, "w", encoding="utf-8") as f: + f.write(CERT["certchain"]) + + # Register template if needed + if cls.hypervisor.lower() == 'simulator': + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor) + else: + cls.template = Template.register( + cls.apiclient, + cls.services["test_templates_cloud_init"][cls.hypervisor.lower()], + zoneid=cls.zone.id, + hypervisor=cls.hypervisor, + ) + cls.template.download(cls.apiclient) + cls._cleanup.append(cls.template) + + if cls.template == FAILED: + assert False, "get_test_template() failed to return template" + + # Create service offering + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["big"] # 512MB memory + ) + + # Create network offering + cls.services["isolated_network_offering"]["egress_policy"] = "true" + cls.network_offering = NetworkOffering.create(cls.apiclient, + cls.services["isolated_network_offering"], + conservemode=True) + cls.network_offering.update(cls.apiclient, state='Enabled') + + cls._cleanup.append(cls.network_offering) + cls._cleanup.append(cls.service_offering) + cls._cleanup.append(cls.account) + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + + def tearDown(self): + super(TestSslOffloading, self).tearDown() + + @classmethod + def tearDownClass(cls): + super(TestSslOffloading, cls).tearDownClass() + if os.path.exists(FULL_CHAIN): + os.remove(FULL_CHAIN) + + def wait_for_service_ready(self, command, expected, retries=60): + output = None + def check_output(): + try: + output = subprocess.check_output(command, shell=True).strip().decode('utf-8') + except Exception as e: + print("Failed to get output of command %s: %s" % (command, e)) + if expected is None: + print("But it is expected") + return True, None + return False, None + print("Output of command %s: \n %s" %(command, output)) + if expected is None: + print("But it is expected to be None") + return False, None + return (expected in output), None + + res = wait_until(10, retries, check_output) + if not res: + self.fail("Failed to wait for http server to show content '%s'. The output is '%s'" % (expected, output)) + return res + + @attr(tags = ["advanced", "advancedns", "smoke"], required_hardware="true") + def test_01_ssl_offloading_isolated_network(self): + """Test to create Load balancing rule with SSL offloading""" + + # Validate: + # 1. Create isolated network and vm instance + # 2. create LB with port 80 -> 80, verify the website (should get expected content) + # 3. create LB with port 443 -> 80, verify the website (should not work) + # 4. add cert to LB with port 443 + # 5. verify the website (should get expected content) + # 6. remove cert from LB with port 443 + # 7. delete SSL certificate + + # Register Userdata + self.userdata = UserData.register(self.apiclient, + name="test-userdata", + userdata=USER_DATA, + account=self.account.name, + domainid=self.account.domainid + ) + + # Upload SSL Certificate + self.sslcert = SslCertificate.create(self.apiclient, + CERT, + name="test-ssl-certificate", + account=self.account.name, + domainid=self.account.domainid) + + # 1. Create network + self.network = Network.create(self.apiclient, + zoneid=self.zone.id, + services=self.services["network"], + domainid=self.domain.id, + account=self.account.name, + networkofferingid=self.network_offering.id) + self.cleanup.append(self.network) + + self.services["virtual_machine"]["networkids"] = [str(self.network.id)] + + # Create vm instance + self.vm_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + userdataid=self.userdata.userdata.id, + serviceofferingid=self.service_offering.id + ) + self.cleanup.append(self.vm_1) + + self.public_ip = PublicIPAddress.create( + self.apiclient, + self.account.name, + self.zone.id, + self.account.domainid, + self.services["virtual_machine"], + self.network.id) + + # 2. create LB with port 80 -> 80, verify the website (should get expected content). + # firewall is open by default + lb_http = { + "name": "http", + "alg": "roundrobin", + "privateport": 80, + "publicport": 80, + "protocol": "tcp" + } + lb_rule_http = LoadBalancerRule.create( + self.apiclient, + lb_http, + self.public_ip.ipaddress.id, + accountid=self.account.name, + domainid=self.domain.id, + networkid=self.network.id + ) + lb_rule_http.assign(self.apiclient, [self.vm_1]) + command = "curl -L --connect-timeout 3 http://%s/" % self.public_ip.ipaddress.ipaddress + # wait 10 minutes until the webpage is available. it returns "503 Service Unavailable" if not available + self.wait_for_service_ready(command, CONTENT, 60) + + # 3. create LB with port 443 -> 80, verify the website (should not work) + # firewall is open by default + lb_https = { + "name": "https", + "alg": "roundrobin", + "privateport": 80, + "publicport": 443, + "protocol": "ssl" + } + lb_rule_https = LoadBalancerRule.create( + self.apiclient, + lb_https, + self.public_ip.ipaddress.id, + accountid=self.account.name, + domainid=self.domain.id, + networkid=self.network.id + ) + lb_rule_https.assign(self.apiclient, [self.vm_1]) + + command = "curl -L --connect-timeout 3 -k --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + # 4. add cert to LB with port 443 + lb_rule_https.assignCert(self.apiclient, self.sslcert.id) + + # 5. verify the website (should get expected content) + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, "SSL certificate problem", 1) + + command = "curl -L --connect-timeout 3 -k --resolve %s:443:%s https://%s/" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + command = "curl -L --connect-timeout 3 --cacert %s --resolve %s:443:%s https://%s/" % (FULL_CHAIN, DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + # 6. remove cert from LB with port 443 + lb_rule_https.removeCert(self.apiclient) + + # 7. delete SSL certificate + self.sslcert.delete(self.apiclient) Review Comment: if the delete fails or an exception occurs before here, will the cert be deleted with the network? and how about the lb-rules? ########## server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java: ########## @@ -1737,11 +1737,19 @@ private void updateWithLbRules(final DomainRouterJoinVO routerJoinVO, final Stri .append(",sourcePortEnd=").append(firewallRuleVO.getSourcePortEnd()); if (firewallRuleVO instanceof LoadBalancerVO) { LoadBalancerVO loadBalancerVO = (LoadBalancerVO) firewallRuleVO; - loadBalancingData.append(",sourceIp=").append(_ipAddressDao.findById(loadBalancerVO.getSourceIpAddressId()).getAddress().toString()) + String sourceIp = _ipAddressDao.findById(loadBalancerVO.getSourceIpAddressId()).getAddress().toString(); + loadBalancingData.append(",sourceIp=").append(sourceIp) .append(",destPortStart=").append(loadBalancerVO.getDefaultPortStart()) .append(",destPortEnd=").append(loadBalancerVO.getDefaultPortEnd()) .append(",algorithm=").append(loadBalancerVO.getAlgorithm()) .append(",protocol=").append(loadBalancerVO.getLbProtocol()); + if (loadBalancerVO.getLbProtocol() != null && loadBalancerVO.getLbProtocol().equals(NetUtils.SSL_PROTO)) { + final LbSslCert sslCert = _lbMgr.getLbSslCert(firewallRuleVO.getId()); + if (sslCert != null && ! sslCert.isRevoked()) { + loadBalancingData.append(",sslcert=").append(sourceIp.replace(".", "_")).append('-') + .append(loadBalancerVO.getSourcePortStart()).append(".pem"); + } + } Review Comment: `conditionallyAddLbProtocol(LoadBalancerVO loadBalancerVO, StringBuilder loadBalancingData)`.. ########## server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java: ########## @@ -2257,12 +2262,21 @@ public LoadBalancer updateLoadBalancerRule(UpdateLoadBalancerRuleCmd cmd) { LoadBalancerVO tmplbVo = _lbDao.findById(lbRuleId); boolean success = _lbDao.update(lbRuleId, lb); - // If algorithm is changed, have to reapply the lb config - if ((algorithm != null) && (tmplbVo.getAlgorithm().compareTo(algorithm) != 0)){ + // If algorithm or lb protocol is changed, have to reapply the lb config + boolean needToReApplyRule = (algorithm != null && algorithm.equals(tmplbVo.getAlgorithm())) + || (lbProtocol != null && lbProtocol.equals(tmplbVo.getLbProtocol())); + if (needToReApplyRule) { try { lb.setState(FirewallRule.State.Add); _lbDao.persist(lb); applyLoadBalancerConfig(lbRuleId); + if (!lb.getLbProtocol().equals(NetUtils.SSL_PROTO)) { + LoadBalancerCertMapVO loadBalancerCertMapVO = _lbCertMapDao.findByLbRuleId(lbRuleId); + if (loadBalancerCertMapVO != null) { + logger.debug("Removing SSL cert for load balancer %s as the new protocol is not ssl but %s", lbRuleId, lb.getLbProtocol()); + _lbCertMapDao.remove(loadBalancerCertMapVO.getId()); + } + } Review Comment: new method .. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@cloudstack.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org