Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ protected List<CreateVolumeMsg> prepareMsg(Map<String, Object> ctx) {
if (disk != null && !isEmpty(disk.getSystemTags())) {
tags.addAll(disk.getSystemTags());
}
Boolean volEnc = disk != null ? disk.getEncrypted() : false;
msg.setEncrypted(volEnc);
} else if (vspec.isData()) {
DiskAO disk = isEmpty(spec.getDataDisks()) ? null :
spec.getDataDisks().size() > dataVolumeIndex ? spec.getDataDisks().get(dataVolumeIndex) : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public void setup() {
} else if (isAttachDataVolume()) {
VolumeVO volume = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, diskAO.getSourceUuid()).find();
volumeInventory = VolumeInventory.valueOf(volume);
setupEncryptExistingVolumeFlow();
setupAttachVolumeFlows();
} else if (diskAO.getSourceUuid() != null && diskAO.getSourceType() != null) {
setupAttachOtherDiskFlows();
Expand Down Expand Up @@ -180,6 +181,7 @@ public void run(final FlowTrigger innerTrigger, Map data) {
msg.setDiskOfferingUuid(diskAO.getDiskOfferingUuid());
msg.setPrimaryStorageUuid(allocatedPrimaryStorageUuid);
msg.setDescription(String.format("vm-%s-data-volume", vmUuid));
msg.setEncrypted(Boolean.TRUE.equals(diskAO.getEncrypted()));
bus.makeLocalServiceId(msg, VolumeConstant.SERVICE_ID);
bus.send(msg, new CloudBusCallBack(innerTrigger) {
@Override
Expand Down Expand Up @@ -328,6 +330,7 @@ public void run(final FlowTrigger innerTrigger, Map data) {
} else {
cmsg.setPrimaryStorageUuid(allocatedPrimaryStorageUuid[0]);
}
cmsg.setEncrypted(Boolean.TRUE.equals(diskAO.getEncrypted()));

bus.makeLocalServiceId(cmsg, VolumeConstant.SERVICE_ID);
bus.send(cmsg, new CloudBusCallBack(innerTrigger) {
Expand Down Expand Up @@ -404,6 +407,56 @@ public void run(MessageReply reply) {
});
}

/**
* When the caller requested an encrypted data volume (DiskAO.encrypted=true) but the
* existing source volume is not yet encrypted, transition the source bits to LUKS
* in place before attaching. Delegates to {@code EncryptVolumeMsg} so the actual
* key/secret/PS-conversion logic lives in {@code VolumeBase} (shared with the
* create-data-volume-from-template flow).
*
* <p>Skipped when:
* <ul>
* <li>{@code DiskAO.encrypted} is false/null, or</li>
* <li>the source volume is already encrypted (no-op transition).</li>
* </ul>
*/
private void setupEncryptExistingVolumeFlow() {
if (!Boolean.TRUE.equals(diskAO.getEncrypted())) {
return;
}
if (volumeInventory != null && Boolean.TRUE.equals(volumeInventory.getEncrypted())) {
return;
}
flow(new NoRollbackFlow() {
String __name__ = String.format("encrypt-existing-data-volume-%s-in-place",
diskAO.getSourceUuid());

@Override
public void run(final FlowTrigger innerTrigger, Map data) {
EncryptVolumeMsg emsg = new EncryptVolumeMsg();
emsg.setVolumeUuid(volumeInventory.getUuid());
emsg.setHostUuid(hostUuid);
emsg.setPurpose("attach-existing-disk-as-encrypted-data-volume");
bus.makeTargetServiceIdByResourceUuid(emsg, VolumeConstant.SERVICE_ID,
volumeInventory.getUuid());
bus.send(emsg, new CloudBusCallBack(innerTrigger) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
innerTrigger.fail(reply.getError());
return;
}
EncryptVolumeReply er = reply.castReply();
if (er.getInventory() != null) {
volumeInventory = er.getInventory();
}
innerTrigger.next();
}
});
}
});
}

private void setupAttachOtherDiskFlows() {
flow(new NoRollbackFlow() {
String __name__ = String.format("attach-other-Disk-to-vm-%s", vmUuid);
Expand Down
12 changes: 12 additions & 0 deletions conf/db/zsv/V5.1.0__schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,15 @@ DELETE FROM `EncryptedResourceKeyRefVO`
ALTER TABLE `EncryptedResourceKeyRefVO`
ADD CONSTRAINT `fkEncryptedResourceKeyRefResourceVO` FOREIGN KEY (`resourceUuid`) REFERENCES `ResourceVO`(`uuid`)
ON DELETE CASCADE;

-- Volume LUKS encryption flag (API opt-in + EncryptedResourceKeyRefVO binding)

ALTER TABLE `zstack`.`VolumeEO` ADD COLUMN `encrypted` tinyint(1) NOT NULL DEFAULT 0;

DROP VIEW IF EXISTS `zstack`.`VolumeVO`;
CREATE VIEW `zstack`.`VolumeVO` AS
SELECT uuid, name, description, primaryStorageUuid, vmInstanceUuid, diskOfferingUuid,
rootImageUuid, installPath, type, status, size, actualSize, deviceId, format, state, createDate, lastOpDate,
isShareable, volumeQos, lastVmInstanceUuid, lastDetachDate, lastAttachDate, protocol, encrypted
FROM `zstack`.`VolumeEO`
WHERE deleted IS NULL;
12 changes: 12 additions & 0 deletions conf/springConfigXml/VolumeManager.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,16 @@
<zstack:extension interface="org.zstack.header.volume.VolumeAttachedJudger"/>
</zstack:plugin>
</bean>

<bean id="DummyVolumeEncryptedResourceKeyBackend"
class="org.zstack.storage.encrypt.DummyVolumeEncryptedResourceKeyBackend"/>

<bean id="VolumeInPlaceEncryptor" class="org.zstack.storage.volume.VolumeInPlaceEncryptor"/>

<bean id="VolumeEncryptedInitialExtension" class="org.zstack.storage.encrypt.VolumeEncryptedInitialExtension">
<zstack:plugin>
<zstack:extension interface="org.zstack.header.volume.PreInstantiateVolumeExtensionPoint" order="-5"/>
<zstack:extension interface="org.zstack.header.volume.AfterInstantiateVolumeExtensionPoint"/>
</zstack:plugin>
</bean>
</beans>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.zstack.header.secret;

import org.zstack.header.host.HostMessage;
import org.zstack.header.log.NoLogging;
import org.zstack.header.message.NeedReplyMessage;

public class SecretHostEnsureLuksSecretFileMsg extends NeedReplyMessage implements HostMessage {
private String hostUuid;
@NoLogging
private String dekBase64;

@Override
public String getHostUuid() {
return hostUuid;
}

public void setHostUuid(String hostUuid) {
this.hostUuid = hostUuid;
}

public String getDekBase64() {
return dekBase64;
}

public void setDekBase64(String dekBase64) {
this.dekBase64 = dekBase64;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.zstack.header.secret;

import org.zstack.header.message.MessageReply;

public class SecretHostEnsureLuksSecretFileReply extends MessageReply {
public static final String ERROR_CODE_KEYS_NOT_ON_DISK = "KEY_AGENT_KEYS_NOT_ON_DISK";
public static final String ERROR_CODE_KEY_FILES_INTEGRITY_MISMATCH = "KEY_AGENT_KEY_FILES_INTEGRITY_MISMATCH";

private String secFilePath;

public String getSecFilePath() {
return secFilePath;
}

public void setSecFilePath(String secFilePath) {
this.secFilePath = secFilePath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.zstack.header.storage.primary;

import org.zstack.header.message.NeedReplyMessage;

/**
* Triggers an in-place LUKS encryption of an existing volume file on primary storage.
* Used after downloading a data-volume template's plain bits to LocalStorage when the
* volume is marked encrypted: the agent converts the plain qcow2/raw at {@link #installPath}
* into a LUKS-encrypted qcow2 (overwriting in place).
*
* The DEK is staged on the host out-of-band (caller stages the secret material file via
* SecretHostEnsureLuksSecretFileMsg and passes the file path here).
*/
public class EncryptVolumeBitsOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage {
private String primaryStorageUuid;
private String hostUuid;
private String volumeUuid;
private String installPath;
private String encryptLuksSecretMaterialFilePath;

@Override
public String getPrimaryStorageUuid() {
return primaryStorageUuid;
}

public void setPrimaryStorageUuid(String primaryStorageUuid) {
this.primaryStorageUuid = primaryStorageUuid;
}

public String getHostUuid() {
return hostUuid;
}

public void setHostUuid(String hostUuid) {
this.hostUuid = hostUuid;
}

public String getVolumeUuid() {
return volumeUuid;
}

public void setVolumeUuid(String volumeUuid) {
this.volumeUuid = volumeUuid;
}

public String getInstallPath() {
return installPath;
}

public void setInstallPath(String installPath) {
this.installPath = installPath;
}

public String getEncryptLuksSecretMaterialFilePath() {
return encryptLuksSecretMaterialFilePath;
}

public void setEncryptLuksSecretMaterialFilePath(String encryptLuksSecretMaterialFilePath) {
this.encryptLuksSecretMaterialFilePath = encryptLuksSecretMaterialFilePath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.zstack.header.storage.primary;

import org.zstack.header.message.MessageReply;

public class EncryptVolumeBitsOnPrimaryStorageReply extends MessageReply {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import org.zstack.header.message.NeedReplyMessage;
import org.zstack.header.message.ReplayableMessage;
import org.zstack.header.volume.VolumeInventory;
import org.zstack.header.volume.VolumeLuksAgentSpec;

public class InstantiateVolumeOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage, ReplayableMessage {
private HostInventory destHost;
private VolumeInventory volume;
private String primaryStorageUuid;
private boolean skipIfExisting;
private String allocatedInstallUrl;
private VolumeLuksAgentSpec volumeLuksAgentSpec;

public String getAllocatedInstallUrl() {
return allocatedInstallUrl;
Expand Down Expand Up @@ -53,6 +55,14 @@ public void setSkipIfExisting(boolean skipIfExisting) {
this.skipIfExisting = skipIfExisting;
}

public VolumeLuksAgentSpec getVolumeLuksAgentSpec() {
return volumeLuksAgentSpec;
}

public void setVolumeLuksAgentSpec(VolumeLuksAgentSpec volumeLuksAgentSpec) {
this.volumeLuksAgentSpec = volumeLuksAgentSpec;
}

@Override
public String getResourceUuid() {
return volume.getUuid();
Expand Down
9 changes: 9 additions & 0 deletions header/src/main/java/org/zstack/header/vm/DiskAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class DiskAO {
private String sourceUuid;
private List<String> systemTags;
private String name;
private Boolean encrypted;

public DiskAO withImage(String imageUuid) {
this.templateUuid = imageUuid;
Expand Down Expand Up @@ -139,6 +140,14 @@ public void setName(String name) {
this.name = name;
}

public Boolean getEncrypted() {
return encrypted;
}

public void setEncrypted(Boolean encrypted) {
this.encrypted = encrypted;
}

public static DiskAO rootDisk() {
DiskAO disk = new DiskAO();
disk.setBoot(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class CreateDataVolumeFromVolumeTemplateMsg extends NeedReplyMessage impl
private String hostUuid;
private String resourceUuid;
private String accountUuid;
private Boolean encrypted;
private APICreateDataVolumeFromVolumeTemplateMsg apiMsg;

public CreateDataVolumeFromVolumeTemplateMsg() {
Expand Down Expand Up @@ -97,4 +98,12 @@ public APICreateDataVolumeFromVolumeTemplateMsg getApiMsg() {
public void setApiMsg(APICreateDataVolumeFromVolumeTemplateMsg amsg) {
this.apiMsg = amsg;
}

public Boolean getEncrypted() {
return encrypted;
}

public void setEncrypted(Boolean encrypted) {
this.encrypted = encrypted;
}
}
11 changes: 11 additions & 0 deletions header/src/main/java/org/zstack/header/volume/CreateVolumeMsg.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class CreateVolumeMsg extends NeedReplyMessage implements VolumeCreateMes
private String format;
private String resourceUuid;
private String protocol;
private Boolean encrypted;

public String getFormat() {
return format;
Expand Down Expand Up @@ -130,4 +131,14 @@ public String getProtocol() {
public void setProtocol(String protocol) {
this.protocol = protocol;
}

@Override
public Boolean getEncrypted() {
return encrypted;
}

@Override
public void setEncrypted(Boolean encrypted) {
this.encrypted = encrypted;
}
}
Loading