|
1 | 1 | package org.zstack.kvm; |
2 | 2 |
|
| 3 | +import org.apache.commons.io.FileUtils; |
3 | 4 | import org.apache.commons.lang.StringUtils; |
4 | 5 | import org.springframework.beans.factory.annotation.Autowired; |
5 | 6 | import org.springframework.web.util.UriComponentsBuilder; |
|
9 | 10 | import org.zstack.compute.vm.VmNicManager; |
10 | 11 | import org.zstack.core.CoreGlobalProperty; |
11 | 12 | import org.zstack.core.ansible.AnsibleFacade; |
| 13 | +import org.zstack.core.jsonlabel.JsonLabel; |
| 14 | +import org.zstack.core.jsonlabel.JsonLabelInventory; |
12 | 15 | import org.zstack.core.cloudbus.CloudBus; |
13 | 16 | import org.zstack.core.cloudbus.CloudBusListCallBack; |
14 | 17 | import org.zstack.core.cloudbus.CloudBusSteppingCallback; |
|
75 | 78 | import org.zstack.resourceconfig.ResourceConfigFacade; |
76 | 79 | import org.zstack.utils.CollectionUtils; |
77 | 80 | import org.zstack.utils.IpRangeSet; |
| 81 | +import org.zstack.utils.ShellUtils; |
78 | 82 | import org.zstack.utils.SizeUtils; |
79 | 83 | import org.zstack.utils.Utils; |
80 | 84 | import org.zstack.utils.data.SizeUnit; |
|
85 | 89 | import org.zstack.utils.logging.CLogger; |
86 | 90 |
|
87 | 91 | import javax.persistence.Tuple; |
| 92 | +import java.io.File; |
88 | 93 | import java.io.IOException; |
89 | 94 | import java.net.InetSocketAddress; |
90 | 95 | import java.net.SocketAddress; |
@@ -122,6 +127,10 @@ public class KVMHostFactory extends AbstractService implements HypervisorFactory |
122 | 127 | HypervisorMessageFactory { |
123 | 128 | private static final CLogger logger = Utils.getLogger(KVMHostFactory.class); |
124 | 129 |
|
| 130 | + private static final String LIBVIRT_TLS_CA_KEY = "libvirtTLSCA"; |
| 131 | + private static final String LIBVIRT_TLS_PRIVATE_KEY = "libvirtTLSPrivateKey"; |
| 132 | + private static final String CA_DIR = "/var/lib/zstack/pki/CA"; |
| 133 | + |
125 | 134 | public static final HypervisorType hypervisorType = new HypervisorType(KVMConstant.KVM_HYPERVISOR_TYPE); |
126 | 135 | public static final VolumeFormat QCOW2_FORMAT = new VolumeFormat(VolumeConstant.VOLUME_FORMAT_QCOW2, hypervisorType); |
127 | 136 | public static final VolumeFormat RAW_FORMAT = new VolumeFormat(VolumeConstant.VOLUME_FORMAT_RAW, hypervisorType); |
@@ -458,8 +467,56 @@ private void processKvmagentPhysicalMemUsageAbnormal(HostProcessPhysicalMemoryUs |
458 | 467 | bus.send(restartKvmAgentMsg); |
459 | 468 | } |
460 | 469 |
|
| 470 | + private void initLibvirtTlsCA() { |
| 471 | + if (CoreGlobalProperty.UNIT_TEST_ON) { |
| 472 | + return; |
| 473 | + } |
| 474 | + |
| 475 | + try { |
| 476 | + ShellUtils.run(String.format("mkdir -p %s", CA_DIR)); |
| 477 | + ShellUtils.run("chown -R zstack:zstack /var/lib/zstack/pki"); |
| 478 | + |
| 479 | + File caFile = new File(CA_DIR + "/cacert.pem"); |
| 480 | + File keyFile = new File(CA_DIR + "/cakey.pem"); |
| 481 | + |
| 482 | + // Local CA missing — generate with openssl |
| 483 | + // NOTE: ShellUtils.run() prepends sudo only to the first command in &&-chains, |
| 484 | + // so each command must be a separate call. |
| 485 | + if (!caFile.exists() || !keyFile.exists()) { |
| 486 | + ShellUtils.run(String.format( |
| 487 | + "openssl genrsa -out %s/cakey.pem 4096", CA_DIR)); |
| 488 | + ShellUtils.run(String.format( |
| 489 | + "openssl req -new -x509 -days 3650 -key %s/cakey.pem " + |
| 490 | + "-out %s/cacert.pem -subj '/O=ZStack/CN=ZStack Libvirt CA'", |
| 491 | + CA_DIR, CA_DIR)); |
| 492 | + ShellUtils.run(String.format("chown zstack:zstack %s/cakey.pem %s/cacert.pem", |
| 493 | + CA_DIR, CA_DIR)); |
| 494 | + ShellUtils.run(String.format("chmod 600 %s/cakey.pem", CA_DIR)); |
| 495 | + ShellUtils.run(String.format("chmod 644 %s/cacert.pem", CA_DIR)); |
| 496 | + } |
| 497 | + |
| 498 | + String ca = FileUtils.readFileToString(caFile).trim(); |
| 499 | + String key = FileUtils.readFileToString(keyFile).trim(); |
| 500 | + |
| 501 | + // createIfAbsent: DB has no record → write; DB has record → return DB value |
| 502 | + JsonLabelInventory caInv = new JsonLabel().createIfAbsent(LIBVIRT_TLS_CA_KEY, ca); |
| 503 | + JsonLabelInventory keyInv = new JsonLabel().createIfAbsent(LIBVIRT_TLS_PRIVATE_KEY, key); |
| 504 | + |
| 505 | + // Use DB as source of truth — overwrite local files (HA: MN2 uses MN1's CA from DB) |
| 506 | + FileUtils.writeStringToFile(caFile, caInv.getLabelValue()); |
| 507 | + FileUtils.writeStringToFile(keyFile, keyInv.getLabelValue()); |
| 508 | + ShellUtils.run(String.format("chmod 600 %s/cakey.pem", CA_DIR)); |
| 509 | + ShellUtils.run(String.format("chmod 644 %s/cacert.pem", CA_DIR)); |
| 510 | + |
| 511 | + logger.info("Libvirt TLS CA initialized and persisted to database"); |
| 512 | + } catch (Exception e) { |
| 513 | + logger.warn("Failed to initialize libvirt TLS CA", e); |
| 514 | + } |
| 515 | + } |
| 516 | + |
461 | 517 | @Override |
462 | 518 | public boolean start() { |
| 519 | + initLibvirtTlsCA(); |
463 | 520 | deployAnsibleModule(); |
464 | 521 | populateExtensions(); |
465 | 522 | configKVMDeviceType(); |
|
0 commit comments