Skip to content

Commit 02eacd0

Browse files
committed
linstor: add support for encryption
This introduces a new encryption mode, instead of a simple bool. Now also storage driver can just provide encrypted volumes to CloudStack.
1 parent 4fee43a commit 02eacd0

File tree

5 files changed

+199
-27
lines changed

5 files changed

+199
-27
lines changed

api/src/main/java/com/cloud/storage/Storage.java

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -135,34 +135,49 @@ public static enum TemplateType {
135135
ISODISK /* Template corresponding to a iso (non root disk) present in an OVA */
136136
}
137137

138+
public enum EncryptionSupport {
139+
/**
140+
* Encryption not supported.
141+
*/
142+
UnSupported,
143+
/**
144+
* Will use hypervisor encryption driver (qemu -> luks)
145+
*/
146+
Hypervisor,
147+
/**
148+
* Storage pool handles encryption and just provides an encrypted volume
149+
*/
150+
Storage
151+
}
152+
138153
public static enum StoragePoolType {
139-
Filesystem(false, true, true), // local directory
140-
NetworkFilesystem(true, true, true), // NFS
141-
IscsiLUN(true, false, false), // shared LUN, with a clusterfs overlay
142-
Iscsi(true, false, false), // for e.g., ZFS Comstar
143-
ISO(false, false, false), // for iso image
144-
LVM(false, false, false), // XenServer local LVM SR
145-
CLVM(true, false, false),
146-
RBD(true, true, false), // http://libvirt.org/storage.html#StorageBackendRBD
147-
SharedMountPoint(true, true, true),
148-
VMFS(true, true, false), // VMware VMFS storage
149-
PreSetup(true, true, false), // for XenServer, Storage Pool is set up by customers.
150-
EXT(false, true, false), // XenServer local EXT SR
151-
OCFS2(true, false, false),
152-
SMB(true, false, false),
153-
Gluster(true, false, false),
154-
PowerFlex(true, true, true), // Dell EMC PowerFlex/ScaleIO (formerly VxFlexOS)
155-
ManagedNFS(true, false, false),
156-
Linstor(true, true, false),
157-
DatastoreCluster(true, true, false), // for VMware, to abstract pool of clusters
158-
StorPool(true, true, true),
159-
FiberChannel(true, true, false); // Fiber Channel Pool for KVM hypervisors is used to find the volume by WWN value (/dev/disk/by-id/wwn-<wwnvalue>)
154+
Filesystem(false, true, EncryptionSupport.Hypervisor), // local directory
155+
NetworkFilesystem(true, true, EncryptionSupport.Hypervisor), // NFS
156+
IscsiLUN(true, false, EncryptionSupport.UnSupported), // shared LUN, with a clusterfs overlay
157+
Iscsi(true, false, EncryptionSupport.UnSupported), // for e.g., ZFS Comstar
158+
ISO(false, false, EncryptionSupport.UnSupported), // for iso image
159+
LVM(false, false, EncryptionSupport.UnSupported), // XenServer local LVM SR
160+
CLVM(true, false, EncryptionSupport.UnSupported),
161+
RBD(true, true, EncryptionSupport.UnSupported), // http://libvirt.org/storage.html#StorageBackendRBD
162+
SharedMountPoint(true, true, EncryptionSupport.Hypervisor),
163+
VMFS(true, true, EncryptionSupport.UnSupported), // VMware VMFS storage
164+
PreSetup(true, true, EncryptionSupport.UnSupported), // for XenServer, Storage Pool is set up by customers.
165+
EXT(false, true, EncryptionSupport.UnSupported), // XenServer local EXT SR
166+
OCFS2(true, false, EncryptionSupport.UnSupported),
167+
SMB(true, false, EncryptionSupport.UnSupported),
168+
Gluster(true, false, EncryptionSupport.UnSupported),
169+
PowerFlex(true, true, EncryptionSupport.Hypervisor), // Dell EMC PowerFlex/ScaleIO (formerly VxFlexOS)
170+
ManagedNFS(true, false, EncryptionSupport.UnSupported),
171+
Linstor(true, true, EncryptionSupport.Storage),
172+
DatastoreCluster(true, true, EncryptionSupport.UnSupported), // for VMware, to abstract pool of clusters
173+
StorPool(true, true, EncryptionSupport.Hypervisor),
174+
FiberChannel(true, true, EncryptionSupport.UnSupported); // Fiber Channel Pool for KVM hypervisors is used to find the volume by WWN value (/dev/disk/by-id/wwn-<wwnvalue>)
160175

161176
private final boolean shared;
162177
private final boolean overProvisioning;
163-
private final boolean encryption;
178+
private final EncryptionSupport encryption;
164179

165-
StoragePoolType(boolean shared, boolean overProvisioning, boolean encryption) {
180+
StoragePoolType(boolean shared, boolean overProvisioning, EncryptionSupport encryption) {
166181
this.shared = shared;
167182
this.overProvisioning = overProvisioning;
168183
this.encryption = encryption;
@@ -177,6 +192,10 @@ public boolean supportsOverProvisioning() {
177192
}
178193

179194
public boolean supportsEncryption() {
195+
return encryption == EncryptionSupport.Hypervisor || encryption == EncryptionSupport.Storage;
196+
}
197+
198+
public EncryptionSupport encryptionSupportMode() {
180199
return encryption;
181200
}
182201
}

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3169,7 +3169,8 @@ public int compare(final DiskTO arg0, final DiskTO arg1) {
31693169
disk.setCacheMode(DiskDef.DiskCacheMode.valueOf(volumeObjectTO.getCacheMode().toString().toUpperCase()));
31703170
}
31713171

3172-
if (volumeObjectTO.requiresEncryption()) {
3172+
if (volumeObjectTO.requiresEncryption() &&
3173+
pool.getType().encryptionSupportMode() == Storage.EncryptionSupport.Hypervisor ) {
31733174
String secretUuid = createLibvirtVolumeSecret(conn, volumeObjectTO.getPath(), volumeObjectTO.getPassphrase());
31743175
DiskDef.LibvirtDiskEncryptDetails encryptDetails = new DiskDef.LibvirtDiskEncryptDetails(secretUuid, QemuObject.EncryptFormat.enumValue(volumeObjectTO.getEncryptFormat()));
31753176
disk.setLibvirtDiskEncryptDetails(encryptDetails);

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import com.cloud.storage.JavaStorageLayer;
5151
import com.cloud.storage.MigrationOptions;
5252
import com.cloud.storage.ScopeType;
53+
import com.cloud.storage.Storage;
5354
import com.cloud.storage.Storage.ImageFormat;
5455
import com.cloud.storage.Storage.StoragePoolType;
5556
import com.cloud.storage.StorageLayer;
@@ -1452,7 +1453,8 @@ protected synchronized void attachOrDetachDisk(final Connect conn, final boolean
14521453
}
14531454
}
14541455

1455-
if (encryptDetails != null) {
1456+
if (encryptDetails != null &&
1457+
attachingPool.getType().encryptionSupportMode() == Storage.EncryptionSupport.Hypervisor) {
14561458
diskdef.setLibvirtDiskEncryptDetails(encryptDetails);
14571459
}
14581460

plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
import com.linbit.linstor.api.DevelopersApi;
2222
import com.linbit.linstor.api.model.ApiCallRc;
2323
import com.linbit.linstor.api.model.ApiCallRcList;
24+
import com.linbit.linstor.api.model.AutoSelectFilter;
25+
import com.linbit.linstor.api.model.LayerType;
2426
import com.linbit.linstor.api.model.Properties;
2527
import com.linbit.linstor.api.model.ResourceDefinition;
2628
import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
2729
import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
2830
import com.linbit.linstor.api.model.ResourceDefinitionCreate;
31+
import com.linbit.linstor.api.model.ResourceGroup;
2932
import com.linbit.linstor.api.model.ResourceGroupSpawn;
3033
import com.linbit.linstor.api.model.ResourceMakeAvailable;
3134
import com.linbit.linstor.api.model.Snapshot;
@@ -34,6 +37,7 @@
3437
import com.linbit.linstor.api.model.VolumeDefinitionModify;
3538

3639
import javax.annotation.Nonnull;
40+
import javax.annotation.Nullable;
3741
import javax.inject.Inject;
3842

3943
import java.util.Arrays;
@@ -43,6 +47,7 @@
4347
import java.util.Map;
4448
import java.util.Objects;
4549
import java.util.Optional;
50+
import java.util.stream.Collectors;
4651

4752
import com.cloud.agent.api.Answer;
4853
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
@@ -105,6 +110,8 @@
105110
import org.apache.cloudstack.storage.volume.VolumeObject;
106111
import org.apache.log4j.Logger;
107112

113+
import java.nio.charset.StandardCharsets;
114+
108115
public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
109116
private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
110117
@Inject private PrimaryDataStoreDao _storagePoolDao;
@@ -393,11 +400,56 @@ private String getRscGrp(StoragePoolVO storagePoolVO) {
393400
storagePoolVO.getUserInfo() : "DfltRscGrp";
394401
}
395402

403+
/**
404+
* Returns the layerlist of the resourceGroup with encryption(LUKS) added above STORAGE.
405+
* If the resourceGroup layer list already contains LUKS this layer list will be returned.
406+
* @param api Linstor developers API
407+
* @param resourceGroup Resource group to get the encryption layer list
408+
* @return layer list with LUKS added
409+
*/
410+
public List<LayerType> getEncryptedLayerList(DevelopersApi api, String resourceGroup) {
411+
try {
412+
List<ResourceGroup> rscGrps = api.resourceGroupList(
413+
Collections.singletonList(resourceGroup), Collections.emptyList(), null, null);
414+
415+
if (rscGrps == null || rscGrps.isEmpty()) {
416+
throw new CloudRuntimeException(
417+
String.format("Resource Group %s not found on Linstor cluster.", resourceGroup));
418+
}
419+
420+
final ResourceGroup rscGrp = rscGrps.get(0);
421+
List<LayerType> layers = Arrays.asList(LayerType.DRBD, LayerType.LUKS, LayerType.STORAGE);
422+
List<String> curLayerStack = rscGrp.getSelectFilter() != null ?
423+
rscGrp.getSelectFilter().getLayerStack() : Collections.emptyList();
424+
if (!(curLayerStack == null || curLayerStack.isEmpty())) {
425+
layers = curLayerStack.stream().map(LayerType::valueOf).collect(Collectors.toList());
426+
if (!layers.contains(LayerType.LUKS)) {
427+
layers.add(layers.size() - 1, LayerType.LUKS); // lowest layer is STORAGE
428+
}
429+
}
430+
return layers;
431+
} catch (ApiException e) {
432+
throw new CloudRuntimeException(
433+
String.format("Resource Group %s not found on Linstor cluster.", resourceGroup));
434+
}
435+
}
436+
396437
private String createResourceBase(
397-
String rscName, long sizeInBytes, String volName, String vmName, DevelopersApi api, String rscGrp) {
438+
String rscName, long sizeInBytes, String volName, String vmName,
439+
@Nullable Long passPhraseId, @Nullable byte[] passPhrase, DevelopersApi api, String rscGrp) {
398440
ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
399441
rscGrpSpawn.setResourceDefinitionName(rscName);
400442
rscGrpSpawn.addVolumeSizesItem(sizeInBytes / 1024);
443+
if (passPhraseId != null) {
444+
AutoSelectFilter asf = new AutoSelectFilter();
445+
List<LayerType> luksLayers = getEncryptedLayerList(api, rscGrp);
446+
asf.setLayerStack(luksLayers.stream().map(LayerType::toString).collect(Collectors.toList()));
447+
rscGrpSpawn.setSelectFilter(asf);
448+
if (passPhrase != null) {
449+
String utf8Passphrase = new String(passPhrase, StandardCharsets.UTF_8);
450+
rscGrpSpawn.setVolumePassphrases(Collections.singletonList(utf8Passphrase));
451+
}
452+
}
401453

402454
try
403455
{
@@ -422,7 +474,8 @@ private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO) {
422474

423475
final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
424476
String deviceName = createResourceBase(
425-
rscName, vol.getSize(), vol.getName(), vol.getAttachedVmName(), linstorApi, rscGrp);
477+
rscName, vol.getSize(), vol.getName(), vol.getAttachedVmName(), vol.getPassphraseId(), vol.getPassphrase(),
478+
linstorApi, rscGrp);
426479

427480
try
428481
{
@@ -463,6 +516,14 @@ private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolV
463516
s_logger.info("Clone resource definition " + cloneRes + " to " + rscName);
464517
ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
465518
cloneRequest.setName(rscName);
519+
if (volumeInfo.getPassphraseId() != null) {
520+
List<LayerType> encryptionLayer = getEncryptedLayerList(linstorApi, getRscGrp(storagePoolVO));
521+
cloneRequest.setLayerList(encryptionLayer);
522+
if (volumeInfo.getPassphrase() != null) {
523+
String utf8Passphrase = new String(volumeInfo.getPassphrase(), StandardCharsets.UTF_8);
524+
cloneRequest.setVolumePassphrases(Collections.singletonList(utf8Passphrase));
525+
}
526+
}
466527
ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
467528
cloneRes, cloneRequest);
468529

@@ -915,6 +976,8 @@ private Answer copyTemplate(DataObject srcData, DataObject dstData) {
915976
tInfo.getSize(),
916977
tInfo.getName(),
917978
"",
979+
null,
980+
null,
918981
api,
919982
getRscGrp(pool));
920983

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.storage.datastore.driver;
18+
19+
import com.linbit.linstor.api.ApiException;
20+
import com.linbit.linstor.api.DevelopersApi;
21+
import com.linbit.linstor.api.model.AutoSelectFilter;
22+
import com.linbit.linstor.api.model.LayerType;
23+
import com.linbit.linstor.api.model.ResourceGroup;
24+
25+
import java.util.Arrays;
26+
import java.util.Collections;
27+
import java.util.List;
28+
29+
import org.junit.Assert;
30+
import org.junit.Before;
31+
import org.junit.Test;
32+
import org.junit.runner.RunWith;
33+
import org.mockito.InjectMocks;
34+
import org.mockito.junit.MockitoJUnitRunner;
35+
36+
import static org.mockito.Mockito.mock;
37+
import static org.mockito.Mockito.when;
38+
39+
@RunWith(MockitoJUnitRunner.class)
40+
public class LinstorPrimaryDataStoreDriverImplTest {
41+
42+
private DevelopersApi api;
43+
44+
@InjectMocks
45+
private LinstorPrimaryDataStoreDriverImpl linstorPrimaryDataStoreDriver;
46+
47+
@Before
48+
public void setUp() {
49+
api = mock(DevelopersApi.class);
50+
}
51+
52+
@Test
53+
public void testGetEncryptedLayerList() throws ApiException {
54+
ResourceGroup dfltRscGrp = new ResourceGroup();
55+
dfltRscGrp.setName("DfltRscGrp");
56+
57+
ResourceGroup bCacheRscGrp = new ResourceGroup();
58+
bCacheRscGrp.setName("BcacheGrp");
59+
AutoSelectFilter asf = new AutoSelectFilter();
60+
asf.setLayerStack(Arrays.asList(LayerType.DRBD.name(), LayerType.BCACHE.name(), LayerType.STORAGE.name()));
61+
asf.setStoragePool("nvmePool");
62+
bCacheRscGrp.setSelectFilter(asf);
63+
64+
ResourceGroup encryptedGrp = new ResourceGroup();
65+
encryptedGrp.setName("EncryptedGrp");
66+
AutoSelectFilter asf2 = new AutoSelectFilter();
67+
asf2.setLayerStack(Arrays.asList(LayerType.DRBD.name(), LayerType.LUKS.name(), LayerType.STORAGE.name()));
68+
asf2.setStoragePool("ssdPool");
69+
encryptedGrp.setSelectFilter(asf2);
70+
71+
when(api.resourceGroupList(Collections.singletonList("DfltRscGrp"), Collections.emptyList(), null, null))
72+
.thenReturn(Collections.singletonList(dfltRscGrp));
73+
when(api.resourceGroupList(Collections.singletonList("BcacheGrp"), Collections.emptyList(), null, null))
74+
.thenReturn(Collections.singletonList(bCacheRscGrp));
75+
when(api.resourceGroupList(Collections.singletonList("EncryptedGrp"), Collections.emptyList(), null, null))
76+
.thenReturn(Collections.singletonList(encryptedGrp));
77+
78+
List<LayerType> layers = linstorPrimaryDataStoreDriver.getEncryptedLayerList(api, "DfltRscGrp");
79+
Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.LUKS, LayerType.STORAGE), layers);
80+
81+
layers = linstorPrimaryDataStoreDriver.getEncryptedLayerList(api, "BcacheGrp");
82+
Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.BCACHE, LayerType.LUKS, LayerType.STORAGE), layers);
83+
84+
layers = linstorPrimaryDataStoreDriver.getEncryptedLayerList(api, "EncryptedGrp");
85+
Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.LUKS, LayerType.STORAGE), layers);
86+
}
87+
}

0 commit comments

Comments
 (0)