package org.openstack.atlas.service.domain.services.impl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openstack.atlas.docs.loadbalancers.api.v1.SslTermination; import org.openstack.atlas.service.domain.entities.LoadBalancer; import org.openstack.atlas.service.domain.entities.LoadBalancerStatus; import org.openstack.atlas.service.domain.exceptions.BadRequestException; import org.openstack.atlas.service.domain.exceptions.EntityNotFoundException; import org.openstack.atlas.service.domain.exceptions.ImmutableEntityException; import org.openstack.atlas.service.domain.exceptions.UnprocessableEntityException; import org.openstack.atlas.service.domain.pojos.ZeusSslTermination; import org.openstack.atlas.service.domain.services.LoadBalancerStatusHistoryService; import org.openstack.atlas.service.domain.services.SslTerminationService; import org.openstack.atlas.service.domain.services.helpers.SslTerminationHelper; import org.openstack.atlas.service.domain.services.helpers.StringHelper; import org.openstack.atlas.service.domain.util.StringUtilities; import org.openstack.atlas.util.ca.zeus.ZeusCrtFile; import org.openstack.atlas.util.ca.zeus.ZeusUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; @Service public class SslTerminationServiceImpl extends BaseService implements SslTerminationService { protected static final ZeusUtils zeusUtils; protected final Log LOG = LogFactory.getLog(SslTerminationServiceImpl.class); static { zeusUtils = new ZeusUtils(); } @Autowired private LoadBalancerStatusHistoryService loadBalancerStatusHistoryService; @Override @Transactional public ZeusSslTermination updateSslTermination(int lbId, int accountId, SslTermination sslTermination, boolean isSync) throws EntityNotFoundException, ImmutableEntityException, BadRequestException, UnprocessableEntityException { ZeusSslTermination zeusSslTermination = new ZeusSslTermination(); ZeusCrtFile zeusCrtFile = null; LoadBalancer dbLoadBalancer = loadBalancerRepository.getByIdAndAccountId(lbId, accountId); //Verify ports and protocols... SslTerminationHelper.isProtocolSecure(dbLoadBalancer); //Grab ports from all vips/shared vips and their lbs' Map<Integer, List<LoadBalancer>> vipPorts = new TreeMap<Integer, List<LoadBalancer>>(); Map<Integer, List<LoadBalancer>> vip6Ports = new TreeMap<Integer, List<LoadBalancer>>(); if (!dbLoadBalancer.getLoadBalancerJoinVipSet().isEmpty()) { vipPorts = virtualIpRepository.getPorts(dbLoadBalancer.getLoadBalancerJoinVipSet().iterator().next().getVirtualIp().getId()); } if (!dbLoadBalancer.getLoadBalancerJoinVip6Set().isEmpty()) { vip6Ports = virtualIpv6Repository.getPorts(dbLoadBalancer.getLoadBalancerJoinVip6Set().iterator().next().getVirtualIp().getId()); } if (!SslTerminationHelper.verifyPortSecurePort(dbLoadBalancer, sslTermination, vipPorts, vip6Ports)) { throw new BadRequestException(String.format("Secure port: '%s' must be unique " + " Ports taken: '%s'", sslTermination.getSecurePort(), buildPortString(vipPorts, vip6Ports))); } if (dbLoadBalancer.isHttpsRedirect() != null && dbLoadBalancer.isHttpsRedirect()) { //Must be secure-only if (sslTermination.isSecureTrafficOnly() != null && !sslTermination.isSecureTrafficOnly()) { throw new BadRequestException("Cannot use 'mixed-mode' SSL termination while HTTPS Redirect is enabled."); } //Must use secure port 443 if (sslTermination.getSecurePort() != null && sslTermination.getSecurePort() != 443) { throw new BadRequestException("Must use secure port 443 with HTTPS Redirect enabled."); } } org.openstack.atlas.service.domain.entities.SslTermination dbTermination = null; try { dbTermination = getSslTermination(dbLoadBalancer.getId(), dbLoadBalancer.getAccountId()); } catch (EntityNotFoundException e) { //this is fine... LOG.warn("LoadBalancer ssl termination could not be found, "); } //we wont make it here if no dbTermination and no cert/key values. dbTermination = SslTerminationHelper.verifyAttributes(sslTermination, dbTermination); if (dbTermination != null) { if (!SslTerminationHelper.modificationStatus(sslTermination, dbLoadBalancer)) { //Validate the certifications and key return the list of errors if there are any, otherwise, pass the transport object to async layer... SslTerminationHelper.sanitizeSslCertKeyEntries(dbTermination); zeusCrtFile = zeusUtils.buildZeusCrtFileLbassValidation(dbTermination.getPrivatekey(), dbTermination.getCertificate(), dbTermination.getIntermediateCertificate()); SslTerminationHelper.verifyCertificationCredentials(zeusCrtFile); } } else { //*Should never happen... LOG.error("The ssl termination service layer could not handle the request, thus not producing a proper sslTermination Object causing this failure..."); throw new UnprocessableEntityException("There was a problem generating the ssl termination configuration, please contact support..."); } LOG.debug("Updating the lb status to pending_update"); if (!isSync) { if (!loadBalancerRepository.testAndSetStatus(dbLoadBalancer.getAccountId(), dbLoadBalancer.getId(), LoadBalancerStatus.PENDING_UPDATE, false)) { String message = StringHelper.immutableLoadBalancer(dbLoadBalancer); LOG.warn(message); throw new ImmutableEntityException(message); } else { //Set status record loadBalancerStatusHistoryService.save(dbLoadBalancer.getAccountId(), dbLoadBalancer.getId(), LoadBalancerStatus.PENDING_UPDATE); } } LOG.info(String.format("Saving ssl termination to the data base for loadbalancer: '%s'", lbId)); sslTerminationRepository.setSslTermination(lbId, dbTermination); LOG.info(String.format("Succesfully saved ssl termination to the data base for loadbalancer: '%s'", lbId)); zeusSslTermination.setSslTermination(dbTermination); if (zeusCrtFile != null) { zeusSslTermination.setCertIntermediateCert(zeusCrtFile.getPublic_cert()); } return zeusSslTermination; } @Override @Transactional public boolean deleteSslTermination(Integer loadBalancerId, Integer accountId) throws EntityNotFoundException, ImmutableEntityException, UnprocessableEntityException, BadRequestException { LOG.info("Deleting ssl termination from the database for loadbalancer: " + loadBalancerId); return sslTerminationRepository.removeSslTermination(loadBalancerId, accountId); } @Override @Transactional public void pseudoDeleteSslTermination(Integer loadBalancerId, Integer accountId) throws EntityNotFoundException, ImmutableEntityException, UnprocessableEntityException, BadRequestException { LoadBalancer dbLoadBalancer = loadBalancerRepository.getByIdAndAccountId(loadBalancerId, accountId); if (dbLoadBalancer.isHttpsRedirect() != null && dbLoadBalancer.isHttpsRedirect()) { //Must not have HTTPS Redirect Enabled throw new BadRequestException("Cannot delete SSL Termination while HTTPS Redirect is enabled. Please disable HTTPS Redirect and retry the operation."); } if (dbLoadBalancer.hasSsl()) { LOG.debug("Updating the lb status to pending_update"); if (!loadBalancerRepository.testAndSetStatus(dbLoadBalancer.getAccountId(), dbLoadBalancer.getId(), LoadBalancerStatus.PENDING_UPDATE, false)) { String message = StringHelper.immutableLoadBalancer(dbLoadBalancer); LOG.warn(message); throw new ImmutableEntityException(message); } else { //Set status record loadBalancerStatusHistoryService.save(dbLoadBalancer.getAccountId(), dbLoadBalancer.getId(), LoadBalancerStatus.ACTIVE); } } else { throw new BadRequestException("SSL termination could not be found for the requested loadbalancer."); } } @Override public org.openstack.atlas.service.domain.entities.SslTermination getSslTermination(Integer lid, Integer accountId) throws EntityNotFoundException { return sslTerminationRepository.getSslTerminationByLbId(lid, accountId); } private String buildPortString(Map<Integer, List<LoadBalancer>> vipPorts, Map<Integer, List<LoadBalancer>> vip6Ports) { final List<Integer> uniques = new ArrayList<Integer>(); for (int i : vipPorts.keySet()) { if (!uniques.contains(i)) { uniques.add(i); } } for (int i : vip6Ports.keySet()) { if (!uniques.contains(i)) { uniques.add(i); } } return StringUtilities.buildDelemtedListFromIntegerArray(uniques.toArray(new Integer[uniques.size()]), ","); } @Override public Map<Integer, org.openstack.atlas.service.domain.entities.SslTermination> getAllMappedByLbId() { Map<Integer, org.openstack.atlas.service.domain.entities.SslTermination> sslMap = new HashMap<Integer, org.openstack.atlas.service.domain.entities.SslTermination>(); List<org.openstack.atlas.service.domain.entities.SslTermination> sslTerms = sslTerminationRepository.getAll(); for (org.openstack.atlas.service.domain.entities.SslTermination sslTerm : sslTerms) { sslMap.put(sslTerm.getLoadbalancer().getId(), sslTerm); } return sslMap; } }