rhtyd commented on a change in pull request #2239: CLOUDSTACK-9993: Securing 
Agents Communications
URL: https://github.com/apache/cloudstack/pull/2239#discussion_r134775294
 
 

 ##########
 File path: server/src/org/apache/cloudstack/ca/CAManagerImpl.java
 ##########
 @@ -0,0 +1,427 @@
+// 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.
+
+package org.apache.cloudstack.ca;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.command.admin.ca.IssueCertificateCmd;
+import org.apache.cloudstack.api.command.admin.ca.ListCAProvidersCmd;
+import org.apache.cloudstack.api.command.admin.ca.ListCaCertificateCmd;
+import org.apache.cloudstack.api.command.admin.ca.ProvisionCertificateCmd;
+import org.apache.cloudstack.api.command.admin.ca.RevokeCertificateCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.ca.CAProvider;
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.poll.BackgroundPollManager;
+import org.apache.cloudstack.poll.BackgroundPollTask;
+import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.alert.AlertManager;
+import com.cloud.certificate.CrlVO;
+import com.cloud.certificate.dao.CrlDao;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.AgentUnavailableException;
+import com.cloud.exception.OperationTimedoutException;
+import com.cloud.host.Host;
+import com.cloud.host.Status;
+import com.cloud.host.dao.HostDao;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.common.base.Strings;
+
+public class CAManagerImpl extends ManagerBase implements CAManager {
+    public static final Logger LOG = Logger.getLogger(CAManagerImpl.class);
+
+    @Inject
+    private CrlDao crlDao;
+    @Inject
+    private HostDao hostDao;
+    @Inject
+    private AgentManager agentManager;
+    @Inject
+    private BackgroundPollManager backgroundPollManager;
+    @Inject
+    private AlertManager alertManager;
+
+    private static CAProvider configuredCaProvider;
+    private static Map<String, CAProvider> caProviderMap = new HashMap<>();
+    private static Map<String, Date> alertMap = new ConcurrentHashMap<>();
+    private static Map<String, X509Certificate> activeCertMap = new 
ConcurrentHashMap<>();
+
+    private List<CAProvider> caProviders;
+
+    private CAProvider getConfiguredCaProvider() {
+        if (configuredCaProvider == null && 
caProviderMap.containsKey(CAProviderPlugin.value())) {
+            configuredCaProvider = caProviderMap.get(CAProviderPlugin.value());
+        }
+        if (configuredCaProvider == null) {
+            throw new CloudRuntimeException("Failed to find default configured 
CA provider plugin");
+        }
+        return configuredCaProvider;
+    }
+
+    private CAProvider getCAProvider(final String provider) {
+        if (Strings.isNullOrEmpty(provider)) {
+            return getConfiguredCaProvider();
+        }
+        final String caProviderName = provider.toLowerCase();
+        if (!caProviderMap.containsKey(caProviderName)) {
+            throw new CloudRuntimeException(String.format("CA provider plugin 
'%s' not found", caProviderName));
+        }
+        final CAProvider caProvider = caProviderMap.get(caProviderName);
+        if (caProvider == null) {
+            throw new CloudRuntimeException(String.format("CA provider plugin 
'%s' returned is null", caProviderName));
+        }
+        return caProvider;
+    }
+
+    ///////////////////////////////////////////////////////////
+    /////////////// CA Manager API Handlers ///////////////////
+    ///////////////////////////////////////////////////////////
+
+    @Override
+    public List<CAProvider> getCaProviders() {
+        return caProviders;
+    }
+
+    @Override
+    public Map<String, X509Certificate> getActiveCertificatesMap() {
+        return activeCertMap;
+    }
+
+    @Override
+    public boolean canProvisionCertificates() {
+        return getConfiguredCaProvider().canProvisionCertificates();
+    }
+
+    @Override
+    public String getCaCertificate(final String caProvider) throws IOException 
{
+        final CAProvider provider = getCAProvider(caProvider);
+        return CertUtils.x509CertificatesToPem(provider.getCaCertificate());
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_ISSUE, 
eventDescription = "issuing certificate", async = true)
+    public Certificate issueCertificate(final String csr, final List<String> 
domainNames, final List<String> ipAddresses, final Integer validityDuration, 
final String caProvider) {
+        CallContext.current().setEventDetails("domain(s): " + domainNames + " 
addresses: " + ipAddresses);
+        final CAProvider provider = getCAProvider(caProvider);
+        Integer validity = CAManager.CertValidityPeriod.value();
+        if (validityDuration != null) {
+            validity = validityDuration;
+        }
+        if (Strings.isNullOrEmpty(csr)) {
+            if (domainNames == null || domainNames.isEmpty()) {
+                throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "No 
domains or CSR provided");
+            }
+            return provider.issueCertificate(domainNames, ipAddresses, 
validity);
+        }
+        return provider.issueCertificate(csr, domainNames, ipAddresses, 
validity);
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_REVOKE, 
eventDescription = "revoking certificate", async = true)
+    public boolean revokeCertificate(final BigInteger certSerial, final String 
certCn, final String caProvider) {
+        CallContext.current().setEventDetails("cert serial: " + certSerial);
+        final CrlVO crl = crlDao.revokeCertificate(certSerial, certCn);
+        if (crl != null && crl.getCertSerial().equals(certSerial)) {
+            final CAProvider provider = getCAProvider(caProvider);
+            return provider.revokeCertificate(certSerial, certCn);
+        }
+        return false;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_PROVISION, 
eventDescription = "provisioning certificate for host", async = true)
+    public boolean provisionCertificate(final Host host, final Boolean 
reconnect, final String caProvider) {
+        if (host == null) {
+            throw new CloudRuntimeException("Unable to find valid host to 
renew certificate for");
+        }
+        CallContext.current().setEventDetails("host id: " + host.getId());
+        CallContext.current().putContextParameter(Host.class, host.getUuid());
+        final String csr;
+        try {
+            csr = generateKeyStoreAndCsr(host, null);
+            if (Strings.isNullOrEmpty(csr)) {
+                return false;
+            }
+            final Certificate certificate = issueCertificate(csr, 
Collections.singletonList(host.getName()), 
Arrays.asList(host.getPrivateIpAddress(), host.getPublicIpAddress(), 
host.getStorageIpAddress()), CAManager.CertValidityPeriod.value(), caProvider);
+            return deployCertificate(host, certificate, reconnect, null);
+        } catch (final AgentUnavailableException | OperationTimedoutException 
e) {
+            LOG.error("Host/agent is not available or operation timed out, 
failed to setup keystore and generate CSR for host/agent id=" + host.getId() + 
", due to: ", e);
+            throw new CloudRuntimeException("Failed to generate keystore and 
get CSR from the host/agent id=" + host.getId());
+        }
+    }
+
+    @Override
+    public String generateKeyStoreAndCsr(final Host host, final Map<String, 
String> sshAccessDetails) throws AgentUnavailableException, 
OperationTimedoutException {
+        final SetupKeyStoreCommand cmd = new 
SetupKeyStoreCommand(CertValidityPeriod.value());
+        if (sshAccessDetails != null && !sshAccessDetails.isEmpty()) {
+            cmd.setAccessDetail(sshAccessDetails);
+        }
+        CallContext.current().setEventDetails("generating keystore and CSR for 
host id: " + host.getId());
+        final SetupKeystoreAnswer answer = (SetupKeystoreAnswer) 
agentManager.send(host.getId(), cmd);
+        return answer.getCsr();
+    }
+
+    @Override
+    public boolean deployCertificate(final Host host, final Certificate 
certificate, final Boolean reconnect, final Map<String, String> 
sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException {
+        final SetupCertificateCommand cmd = new 
SetupCertificateCommand(certificate);
+        if (sshAccessDetails != null && !sshAccessDetails.isEmpty()) {
+            cmd.setAccessDetail(sshAccessDetails);
+        }
+        CallContext.current().setEventDetails("deploying certificate for host 
id: " + host.getId());
+        final SetupCertificateAnswer answer = (SetupCertificateAnswer) 
agentManager.send(host.getId(), cmd);
+        if (answer.getResult()) {
+            CallContext.current().setEventDetails("successfully deployed 
certificate for host id: " + host.getId());
+        } else {
+            CallContext.current().setEventDetails("failed to deploy 
certificate for host id: " + host.getId());
+        }
+
+        if (answer.getResult()) {
+            getActiveCertificatesMap().put(host.getPrivateIpAddress(), 
certificate.getClientCertificate());
+            if (sshAccessDetails == null && reconnect != null && reconnect) {
+                LOG.info(String.format("Successfully setup certificate on 
host, reconnecting with agent with id=%d, name=%s, address=%s",
+                        host.getId(), host.getName(), 
host.getPublicIpAddress()));
+                return agentManager.reconnect(host.getId());
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void purgeHostCertificate(final Host host) {
+        if (host == null) {
+            return;
+        }
+        final String privateAddress = host.getPrivateIpAddress();
+        final String publicAddress = host.getPublicIpAddress();
+        final Map<String, X509Certificate> activeCertsMap = 
getActiveCertificatesMap();
+        if (!Strings.isNullOrEmpty(privateAddress) && 
activeCertsMap.containsKey(privateAddress)) {
+            activeCertsMap.remove(privateAddress);
+        }
+        if (!Strings.isNullOrEmpty(publicAddress) && 
activeCertsMap.containsKey(publicAddress)) {
+            activeCertsMap.remove(publicAddress);
+        }
+    }
+
+    @Override
+    public void sendAlert(final Host host, final String subject, final String 
message) {
+        if (host == null) {
+            return;
+        }
+        alertManager.sendAlert(AlertManager.AlertType.ALERT_TYPE_CA_CERT,
+                host.getDataCenterId(), host.getPodId(), subject, message);
+    }
+
+    @Override
+    public SSLEngine createSSLEngine(final SSLContext sslContext, final String 
remoteAddress) throws GeneralSecurityException, IOException {
+        if (sslContext == null) {
+            throw new CloudRuntimeException("SSLContext provided to create 
SSLEngine is null, aborting");
+        }
+        if (Strings.isNullOrEmpty(remoteAddress)) {
+            throw new CloudRuntimeException("Remote client address connecting 
to mgmt server cannot be empty/null");
+        }
+        return getConfiguredCaProvider().createSSLEngine(sslContext, 
remoteAddress, getActiveCertificatesMap());
+    }
+
+    ////////////////////////////////////////////////////
+    /////////////// CA Manager Setup ///////////////////
+    ////////////////////////////////////////////////////
+
+    public static final class CABackgroundTask extends ManagedContextRunnable 
implements BackgroundPollTask {
+        private CAManager caManager;
+        private HostDao hostDao;
+
+        public CABackgroundTask(final CAManager caManager, final HostDao 
hostDao) {
+            this.caManager = caManager;
+            this.hostDao = hostDao;
+        }
+
+        @Override
+        protected void runInContext() {
 
 Review comment:
   I would avoid, not huge.
 
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to