package com.intel.mountwilson.Service;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.intel.mountwilson.common.MCPConfig;
import com.intel.mountwilson.common.MCPersistenceManager;
import com.intel.mountwilson.common.ManagementConsolePortalException;
import com.intel.mountwilson.datamodel.ApiClientDetails;
import com.intel.mountwilson.datamodel.ApiClientListType;
import com.intel.mountwilson.datamodel.HostDetails;
import com.intel.mountwilson.util.ConnectionUtil;
import com.intel.mtwilson.ApiClient;
import com.intel.mtwilson.api.*;
import com.intel.mtwilson.agent.vmware.VMwareClient;
import com.intel.dcsg.cpg.x509.X509Util;
import com.intel.mtwilson.datatypes.*;
import com.intel.mtwilson.ms.controller.ApiClientX509JpaController;
import com.intel.mtwilson.ms.controller.MwPortalUserJpaController;
import com.intel.mtwilson.ms.data.ApiClientX509;
import com.intel.mtwilson.ms.data.MwPortalUser;
import com.intel.dcsg.cpg.x500.DN;
import com.intel.mtwilson.tls.policy.TlsPolicyChoice;
import com.intel.mtwilson.tls.policy.TlsPolicyDescriptor;
import com.intel.mtwilson.user.management.rest.v2.model.UserComment;
import com.vmware.vim25.InvalidProperty;
import com.vmware.vim25.RuntimeFault;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.logging.Level;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ManagementConsoleServicesImpl implements IManagementConsoleServices {
private static final Logger log = LoggerFactory.getLogger(ManagementConsoleServicesImpl.class.getName());
private MCPersistenceManager mcManager;
private MwPortalUserJpaController keystoreJpa;
private ApiClientX509JpaController apiClientJpa;
private final ObjectMapper yaml;
public ManagementConsoleServicesImpl() {
mcManager = new MCPersistenceManager();
keystoreJpa = new MwPortalUserJpaController(mcManager.getEntityManagerFactory("MSDataPU")); // fix bug 677, MwPortalUser is in MSDataPU, not in ASDataPU
apiClientJpa = new ApiClientX509JpaController(mcManager.getEntityManagerFactory("MSDataPU"));
yaml = createYamlMapper();
}
/**
*
* @param hostDetailsObj
* @param apiObj
* @return
* @throws ManagementConsolePortalException
*/
@Override
public boolean saveWhiteListConfiguration(HostDetails hostDetailsObj, HostConfigData hostConfig, ApiClient apiObj)
throws ManagementConsolePortalException, MalformedURLException {
log.info("ManagementConsoleServicesImpl.saveWhiteListConfiguration >>");
ManagementService msAPIObj = (ManagementService) apiObj;
// Create the host config object to be sent to the Management API for white list configuration
HostConfigData hostConfigObj = hostConfig;
TxtHostRecord hostRecord = new TxtHostRecord();
hostRecord.HostName = hostDetailsObj.getHostName();
// Bug:726: Port number was not being displayed in the UI since it was not being set in the Port field. Because of this issue, the port # was not getting stored in the DB.
if (hostDetailsObj.getHostPortNo() != null && !hostDetailsObj.getHostPortNo().isEmpty()) {
hostRecord.Port = Integer.parseInt(hostDetailsObj.getHostPortNo());
}
// Bug 614: Using connection strings for all kinds of hosts.
ConnectionString connStr;
if (hostDetailsObj.getHostType().equalsIgnoreCase(Vendor.INTEL.toString())) {
connStr = ConnectionString.forIntel(hostDetailsObj.getHostName(), Integer.parseInt(hostDetailsObj.getHostPortNo())); //new ConnectionString(Vendor.INTEL, hostDetailsObj.getHostName(), Integer.parseInt(hostDetailsObj.getHostPortNo()));
} else {
// we need to handle both the VMware and Citrix connection strings in the same way. Since the user
// will be providing the entire connection string, we do not need to create one similar to the Intel one.
connStr = new ConnectionString(Vendor.valueOf(hostDetailsObj.getHostType().toUpperCase()), hostDetailsObj.getvCenterString());
}
hostRecord.AddOn_Connection_String = connStr.getConnectionStringWithPrefix();
// hostRecord.tlsPolicyChoice = hostDetailsObj.?????
/* if (hostDetailsObj.isVmWareType()) {
hostRecord.HostName = hostDetailsObj.getHostName();
hostRecord.AddOn_Connection_String = hostDetailsObj.getvCenterString();
} else {
hostRecord.HostName = hostDetailsObj.getHostName();
hostRecord.IPAddress = hostDetailsObj.getHostName();
hostRecord.Port = Integer.parseInt(hostDetailsObj.getHostPortNo());
} */
if( hostDetailsObj.getTlsPolicyId() != null ) {
log.debug("saveWhiteListConfiguration tls policy id {}", hostDetailsObj.getTlsPolicyId());
hostRecord.tlsPolicyChoice = new TlsPolicyChoice();
hostRecord.tlsPolicyChoice.setTlsPolicyId(hostDetailsObj.getTlsPolicyId());
}
else if( hostDetailsObj.getTlsPolicyType() != null ) {
log.debug("saveWhiteListConfiguration tls policy type {}", hostDetailsObj.getTlsPolicyType());
ArrayList<String> data = new ArrayList<>();
data.add(hostDetailsObj.getTlsPolicyData());
hostRecord.tlsPolicyChoice = new TlsPolicyChoice();
hostRecord.tlsPolicyChoice.setTlsPolicyDescriptor(new TlsPolicyDescriptor());
hostRecord.tlsPolicyChoice.getTlsPolicyDescriptor().setPolicyType(hostDetailsObj.getTlsPolicyType());
hostRecord.tlsPolicyChoice.getTlsPolicyDescriptor().setData(data);
}
hostConfigObj.setTxtHostRecord(hostRecord);
try {
boolean result = msAPIObj.configureWhiteList(hostConfigObj);
return result;
} catch (Exception e) {
log.error("Failed to configure whitelist: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
}
/**
*
* @param vCenterConnection
* @return
* @throws ManagementConsolePortalException
*/
@Override
public List<String> getDatacenterNames(VMwareClient client) throws ManagementConsolePortalException {
log.info("ManagementConsoleServicesImpl.getDatacenters >>");
//logger.info("ClusterName : "+clusterName +", vCenter Connection String : "+vCenterConnection);
try {
List<String> datacenters = client.getDatacenterNames();
return datacenters;
} catch (Exception e) {
log.error("Failed to get datacenter information from vmware: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
}
/**
*
* @param datacenter
* @param vCenterConnection
* @return
* @throws ManagementConsolePortalException
*/
@Override
public List<String> getClusterNamesWithDC(VMwareClient client) throws ManagementConsolePortalException {
log.info("ManagementConsoleServicesImpl.getClusters >>");
//logger.info("ClusterName : "+clusterName +", vCenter Connection String : "+vCenterConnection);
try {
List<String> clusters = client.getClusterNamesWithDC();
return clusters;
} catch (Exception e) {
log.error("Failed to get cluster information from vmware: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
}
/**
*
* @param clusterName
* @param vCenterConnection
* @return
* @throws ManagementConsolePortalException
*/
@Override
public List<HostDetails> getHostNamesForCluster(VMwareClient client, String clusterName) throws ManagementConsolePortalException {
log.info("ManagementConsoleServicesImpl.getHostEntryFromVMWareCluster >>");
//logger.info("ClusterName : "+clusterName +", vCenter Connection String : "+vCenterConnection);
try {
List<TxtHostRecord> hostList;
List<HostDetails>hostVos = new ArrayList<HostDetails>();
try {
hostList = client.getHostNamesForCluster(clusterName);
} catch (Exception e) {
log.error("Failed to get host information from vmware cluster: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
for (TxtHostRecord hostObj : hostList) {
log.debug("TxtHostRecord found in hostList: " + hostObj.HostName);
HostDetails mcObj = new HostDetails();
mcObj.setHostName(hostObj.HostName);
mcObj.setvCenterString(hostObj.AddOn_Connection_String);
mcObj.setVmWareType(true);
hostVos.add(mcObj);
}
return hostVos;
} catch (Exception e) {
log.error("Failed to get host information from vmware cluster: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
}
/**
*
* @param dataVOList
* @param apiObj
* @return
* @throws ManagementConsolePortalException
*/
@Override
public HostDetails registerNewHost(HostDetails hostDetailList, ApiClient apiObj) throws ManagementConsolePortalException {
log.info("ManagementConsoleServicesImpl.registerNewHost >>");
log.debug("Host To Be Register >>" + hostDetailList);
ManagementService msAPIObj = (ManagementService) apiObj;
// Create the host object to be sent to the Management API for host registration
TxtHostRecord hostObj = new TxtHostRecord();
if (hostDetailList.isVmWareType()) {
hostObj.HostName = hostDetailList.getHostName();
hostObj.AddOn_Connection_String = hostDetailList.getvCenterString();
} else {
hostObj.HostName = hostDetailList.getHostName();
hostObj.IPAddress = hostDetailList.getHostName();
hostObj.Port = Integer.parseInt(hostDetailList.getHostPortNo());
}
HostConfigData configData = new HostConfigData();
configData.setBiosWLTarget(HostWhiteListTarget.valueOf(hostDetailList.getBiosWLTarget()));
configData.setVmmWLTarget(HostWhiteListTarget.valueOf(hostDetailList.getVmmWLtarget()));
configData.setTxtHostRecord(hostObj);
try {
boolean result = msAPIObj.registerHost(configData);
if (result) {
hostDetailList.setStatus("Successfully registered the host.");
}
} catch (Exception e) {
log.error("Failed to register the host: {}", e.getMessage());
// Bug: 441 - We should not be throwing exception here. Instead setting the error correctly
hostDetailList.setStatus(StringEscapeUtils.escapeHtml(e.getMessage()));
}
return hostDetailList;
}
/**
*
* @param dataVO
* @param apiObj
* @param approve
* @return
* @throws ManagementConsolePortalException
*/
@Override
public boolean updateRequest(ApiClientDetails apiClientDetailsObj, ApiClient apiObj, boolean approve) throws ManagementConsolePortalException {
log.info("ManagementConsoleServicesImpl.updateRequest >>");
try {
ManagementService msAPIObj = (ManagementService) apiObj;
ApiClientUpdateRequest apiUpdateObj = new ApiClientUpdateRequest();
try {
apiUpdateObj.fingerprint = Hex.decodeHex(apiClientDetailsObj.getFingerprint().toCharArray());
} catch (DecoderException ex) {
throw ex;
}
if (approve) {
apiUpdateObj.enabled = true;
apiUpdateObj.roles = (String[]) apiClientDetailsObj.getRequestedRoles().toArray(new String[0]);
apiUpdateObj.status = "APPROVED";
if (apiClientDetailsObj.getComment() != null) {
apiUpdateObj.comment = apiClientDetailsObj.getComment();
}
} else {
apiUpdateObj.enabled = false;
apiUpdateObj.roles = (String[]) apiClientDetailsObj.getRequestedRoles().toArray(new String[0]);
apiUpdateObj.status = "REJECTED";
apiUpdateObj.comment = apiClientDetailsObj.getComment();
}
boolean result = msAPIObj.updateApiClient(apiUpdateObj);
return result;
} catch (Exception e) {
log.error("Update failed: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
}
/**
*
* @param fingerprint
* @param apiObj
* @return
* @throws ManagementConsolePortalException
*/
@Override
public boolean deleteSelectedRequest(String fingerprint, ApiClient apiObj) throws ManagementConsolePortalException {
log.info("ManagementConsoleServicesImpl.deleteSelectedRequest >>");
log.debug("API Client being deleted >> " + fingerprint);
try {
ManagementService msAPIObj = (ManagementService) apiObj;
byte[] decodedFP;
try {
decodedFP = Hex.decodeHex(fingerprint.toCharArray());
} catch (DecoderException ex) {
throw ex;
}
// We are cleaning up the portal user table only if the API client was successfully deleted. Since the portal is calling the JPA
// controller directly, there is no way to enforce shiro authentication. In cases where the user did not have the right roles and
// called into this function, the portal user would be deleted but not the user entry in the api_client table and the mw_user table.
// THe below API would return true if the user had access and user was successfully deleted. Then only we will clean up the portal user table.
boolean result = msAPIObj.deleteApiClient(decodedFP); // only marks it as deleted (must retain the record for audits)
log.debug("deleteSelectedRequest: return value is {}", result);
if (result == true) {
try {
// fix bug #677
ApiClientX509 clientRecord = apiClientJpa.findApiClientX509ByFingerprint(decodedFP);
if (clientRecord != null) {
log.debug("Found the user using api client JPA.");
X509Certificate clientCert = X509Util.decodeDerCertificate(clientRecord.getCertificate());
DN dn = new DN(clientCert.getSubjectX500Principal().getName());
String username = dn.getCommonName();
MwPortalUser portalUser = keystoreJpa.findMwPortalUserByUserName(username);
// List<MwPortalUser> portalUsers = keystoreJpa.findMwPortalUserByUsernameEnabled(username);
// in case there was more than one (shouldn't happen!!) with the same username who is ENABLED, identify the right one via fingerprint
keystoreJpa.destroy(portalUser.getId());
// }
// keystoreJpa.destroy(clientRecord.getId()); // actually deletes the user keystore w/ private key bug #677 trying to delete a MwPortalUser keystore using the ID of an ApiClientX509 record
} else {
log.debug("Did not find the user using the api client JPA.");
}
} catch (Exception ex) {
log.error("Error during the clean up of the portal user DB. {}", ex.getMessage());
}
}
return result;
} catch (Exception e) {
log.error("Delete failed: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
}
/**
*
* @param apiObj
* @return
* @throws ManagementConsolePortalException
*/
public Role[] getAllRoles(ApiClient apiObj) throws ManagementConsolePortalException {
log.info("ManagementConsoleServicesImpl.getAllRoles >>");
try {
ManagementService msAPIObj = (ManagementService) apiObj;
Role[] roleList = msAPIObj.listAvailableRoles();
return roleList;
} catch (Exception e) {
log.error("Failed to get list of roles: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
}
/**
*
* @param apiObj
* @param apiType
* @return
* @throws ManagementConsolePortalException
*/
@Override
public List<ApiClientDetails> getApiClients(ApiClient apiObj, ApiClientListType apiType) throws ManagementConsolePortalException {
log.info("ManagementConsoleServicesImpl.getApprovedRequest >>");
List<ApiClientDetails> apiClientList = new ArrayList<>();
List<ApiClientInfo> apiListFromDB = null;
try {
// Retrieve the pending approvals
ManagementService msAPIObj = (ManagementService) apiObj;
ApiClientSearchCriteria apiSearchObj = new ApiClientSearchCriteria();
if (apiType == ApiClientListType.ALL) {
apiSearchObj.enabledEqualTo = true;
apiSearchObj.statusEqualTo = ApiClientStatus.APPROVED.toString();
apiListFromDB = msAPIObj.searchApiClients(apiSearchObj);
apiSearchObj.enabledEqualTo = false;
apiSearchObj.statusEqualTo = ApiClientStatus.REJECTED.toString();
apiListFromDB.addAll(msAPIObj.searchApiClients(apiSearchObj));
} else if (apiType == ApiClientListType.DELETE) {
apiSearchObj.enabledEqualTo = true;
apiSearchObj.statusEqualTo = "APPROVED";
apiListFromDB = msAPIObj.searchApiClients(apiSearchObj);
} else if (apiType == ApiClientListType.EXPIRING) {
int expirationMonths = MCPConfig.getConfiguration().getInt("mtwilson.mc.apiKeyExpirationNoticeInMonths", 3);
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MONTH, expirationMonths);
apiSearchObj.expiresBefore = cal.getTime();
apiListFromDB = msAPIObj.searchApiClients(apiSearchObj);
} else if (apiType == ApiClientListType.PENDING) {
apiSearchObj.enabledEqualTo = false;
apiSearchObj.statusEqualTo = "PENDING";
apiListFromDB = msAPIObj.searchApiClients(apiSearchObj);
}
} catch (Exception e) {
log.error("Failed to list API clients: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
try {
if (apiListFromDB != null) {
for (ApiClientInfo apiClientObj : apiListFromDB) {
ApiClientDetails apiClientDetailObj = new ApiClientDetails();
apiClientDetailObj.setName(apiClientObj.name);
apiClientDetailObj.setFingerprint(new String(Hex.encodeHex(apiClientObj.fingerprint)));
apiClientDetailObj.setExpires(apiClientObj.expires);
apiClientDetailObj.setRoles(Arrays.asList(apiClientObj.roles));
apiClientDetailObj.setIssuer(apiClientObj.issuer);
apiClientDetailObj.setStatus(apiClientObj.status);
// apiClientDetailObj.setComment(apiClientObj.comment);
try {
if( apiClientObj.comment != null ) {
UserComment comment = yaml.readValue(apiClientObj.comment, UserComment.class);
if( comment.roles != null ) {
apiClientDetailObj.setRequestedRoles(new ArrayList<String>(comment.roles));
}
apiClientDetailObj.setComment("");
}
}
catch(Exception e) {
log.error("Cannot parse user comment: {}", apiClientObj.comment, e);
apiClientDetailObj.setRequestedRoles(new ArrayList<String>());
apiClientDetailObj.setComment(apiClientObj.comment);
}
apiClientList.add(apiClientDetailObj);
}
}
} catch (Exception e) {
log.error("Failed to compile list of API clients: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
return apiClientList;
}
// SEE ALSO: ApiClientBO.creatYamlMapper() in mtwilson-management
private ObjectMapper createYamlMapper() {
YAMLFactory yamlFactory = new YAMLFactory();
yamlFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
yamlFactory.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
ObjectMapper mapper = new ObjectMapper(yamlFactory);
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy());
return mapper;
}
/**
*
* @param apiObj
* @return
* @throws ManagementConsolePortalException
*/
@Override
public List<ApiClientDetails> getCADetails(ApiClient apiObj) throws ManagementConsolePortalException {
return null;
}
/**
*
* @param apiObj
* @param hostRecords
* @return
* @throws ManagementConsolePortalException
*/
@Override
public HostConfigResponseList registerHosts(ApiClient apiObj, List<HostDetails> hostRecords) throws ManagementConsolePortalException, MalformedURLException {
log.info("ManagementConsoleServicesImpl.registerHosts >>");
log.debug("# of hosts to be registered >> " + hostRecords.size());
List<HostConfigData> hostConfigList = new ArrayList<HostConfigData>();
HostConfigDataList hostList = new HostConfigDataList();
ManagementService msAPIObj = (ManagementService) apiObj;
// We now need to create the actual HostConfigData objects using the HostDetail object
for (HostDetails hostRecord : hostRecords) {
TxtHostRecord hostTxtObj = new TxtHostRecord();
hostTxtObj.HostName = hostRecord.getHostName();
// Bug:726: Port number was not being displayed in the UI since it was not being set in the Port field. Because of this issue, the port # was not getting stored in the DB.
if (hostRecord.getHostPortNo() != null && !hostRecord.getHostPortNo().isEmpty()) {
hostTxtObj.Port = Integer.parseInt(hostRecord.getHostPortNo());
}
// Bug 614: Using connection strings for all kinds of hosts.
ConnectionString connStr;
if (hostRecord.getHostType().equalsIgnoreCase(Vendor.INTEL.toString())) {
connStr = ConnectionString.forIntel(hostRecord.getHostName(), Integer.parseInt(hostRecord.getHostPortNo())); //new ConnectionString(Vendor.INTEL, hostRecord.getHostName(), Integer.parseInt(hostRecord.getHostPortNo()));
} else {
// we need to handle both the VMware and Citrix connection strings in the same way. Since the user
// will be providing the entire connection string, we do not need to create one similar to the Intel one.
connStr = new ConnectionString(Vendor.valueOf(hostRecord.getHostType().toUpperCase()), hostRecord.getvCenterString());
}
hostTxtObj.AddOn_Connection_String = connStr.getConnectionStringWithPrefix();
/*if (hostRecord.isVmWareType()) {
hostTxtObj.HostName = hostRecord.getHostName();
hostTxtObj.AddOn_Connection_String = hostRecord.getvCenterString();
} else {
hostTxtObj.HostName = hostRecord.getHostName();
hostTxtObj.IPAddress = hostRecord.getHostName();
hostTxtObj.Port = Integer.parseInt(hostRecord.getHostPortNo());
}*/
hostTxtObj.tlsPolicyChoice = getTlsPolicyChoice(hostRecord);
HostConfigData configData = new HostConfigData();
configData.setBiosWLTarget(HostWhiteListTarget.getBIOSWhiteListTarget(hostRecord.getBiosWLTarget()));
configData.setVmmWLTarget(HostWhiteListTarget.getVMMWhiteListTarget(hostRecord.getVmmWLtarget()));
configData.setTxtHostRecord(hostTxtObj);
hostConfigList.add(configData);
}
hostList.setHostRecords(hostConfigList);
try {
HostConfigResponseList results = msAPIObj.registerHosts(hostList);
return results;
} catch (Exception e) {
log.error("Failed to register hosts: {}", e.getMessage());
throw ConnectionUtil.handleManagementConsoleException(e);
}
}
private TlsPolicyChoice getTlsPolicyChoice(HostDetails hostRecord) {
TlsPolicyChoice tlsPolicyChoice = new TlsPolicyChoice();
TlsPolicyDescriptor tlsPolicyDescriptor = new TlsPolicyDescriptor();
if( hostRecord.getTlsPolicyId() != null && !hostRecord.getTlsPolicyId().isEmpty() ) {
tlsPolicyChoice.setTlsPolicyId(hostRecord.getTlsPolicyId());
}
if( hostRecord.getTlsPolicyType() != null && !hostRecord.getTlsPolicyType().isEmpty() ) {
tlsPolicyDescriptor.setPolicyType(hostRecord.getTlsPolicyType());
}
if( hostRecord.getTlsPolicyData() != null && !hostRecord.getTlsPolicyData().isEmpty() ) {
tlsPolicyDescriptor.setData(new ArrayList<String>());
tlsPolicyDescriptor.getData().add(hostRecord.getTlsPolicyData());
}
tlsPolicyChoice.setTlsPolicyDescriptor(tlsPolicyDescriptor);
return tlsPolicyChoice;
}
}