Copilot commented on code in PR #12711:
URL: https://github.com/apache/cloudstack/pull/12711#discussion_r2871992971


##########
engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java:
##########
@@ -934,4 +971,12 @@ public VolumeVO findByLastIdAndState(long lastVolumeId, 
State ...states) {
         sc.and(sc.entity().getState(), SearchCriteria.Op.IN,  (Object[]) 
states);
         return sc.find();
     }
+
+    @Override
+    public boolean existsWithKmsKey(long kmsKeyId) {
+        SearchCriteria<VolumeVO> sc = AllFieldsSearch.create();
+        sc.setParameters("kmsKeyId", kmsKeyId);
+        sc.setParameters("notDestroyed", Volume.State.Expunged, 
Volume.State.Destroy);
+        return findOneBy(sc) != null;
+    }

Review Comment:
   `existsWithKmsKey()` sets a `kmsKeyId` parameter on `AllFieldsSearch`, but 
`AllFieldsSearch` is not configured with a `kmsKeyId` criterion in the 
constructor. This will trigger an assertion/runtime error when the method is 
called. Add `AllFieldsSearch.and("kmsKeyId", ...getKmsKeyId(), Op.EQ)` (or use 
a dedicated SearchBuilder) before using `setParameters("kmsKeyId", ...)`.



##########
engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java:
##########
@@ -929,9 +955,29 @@ public void doInTransactionWithoutResult(TransactionStatus 
status) {
 
     /**
      * Looks up passphrase from underlying volume.
-     * @return passphrase as bytes
+     * Supports both legacy passphrase-based encryption and KMS-based 
encryption.
+     * @return passphrase/DEK as base64-encoded bytes (UTF-8 bytes of base64 
string)
      */
     public byte[] getPassphrase() {
+        // First check for KMS-encrypted volume
+        if (volumeVO.getKmsWrappedKeyId() != null) {
+            try {
+                // Unwrap the DEK from KMS (returns raw binary bytes)
+                byte[] dekBytes = 
kmsManager.unwrapKey(volumeVO.getKmsWrappedKeyId());
+                // Base64-encode the DEK for consistency with legacy 
passphrases
+                // and for use with qemu-img which expects base64 format
+                String base64Dek = 
java.util.Base64.getEncoder().encodeToString(dekBytes);
+                // Zeroize the raw DEK bytes
+                java.util.Arrays.fill(dekBytes, (byte) 0);
+                // Return UTF-8 bytes of the base64 string
+                return 
base64Dek.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+            } catch (org.apache.cloudstack.framework.kms.KMSException e) {
+                logger.error("Failed to unwrap KMS key for volume {}: {}", 
volumeVO.getId(), e.getMessage());
+                return new byte[0];
+            }

Review Comment:
   `getPassphrase()` swallows `KMSException` during unwrap and returns an empty 
byte array. Returning an empty passphrase can cause downstream 
encryption/secret setup to proceed as if the volume is unencrypted or fail in 
non-obvious ways. Prefer failing the operation by rethrowing (e.g., as 
`CloudRuntimeException`) so callers don't accidentally continue with invalid 
encryption material.



##########
engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDaoImpl.java:
##########
@@ -0,0 +1,71 @@
+// 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.kms.dao;
+
+import com.cloud.utils.db.Filter;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.kms.KMSWrappedKeyVO;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class KMSWrappedKeyDaoImpl extends GenericDaoBase<KMSWrappedKeyVO, 
Long> implements KMSWrappedKeyDao {
+
+    private final SearchBuilder<KMSWrappedKeyVO> allFieldSearch;
+
+    public KMSWrappedKeyDaoImpl() {
+        super();
+
+        allFieldSearch = createSearchBuilder();
+        allFieldSearch.and("kmsKeyId", allFieldSearch.entity().getKmsKeyId(), 
SearchCriteria.Op.EQ);
+        allFieldSearch.and("kekVersionId", 
allFieldSearch.entity().getKekVersionId(), SearchCriteria.Op.EQ);
+        allFieldSearch.and("zoneId", allFieldSearch.entity().getZoneId(), 
SearchCriteria.Op.EQ);
+        allFieldSearch.and("kmsKeyId", allFieldSearch.entity().getKmsKeyId(), 
SearchCriteria.Op.EQ);
+        allFieldSearch.done();

Review Comment:
   `allFieldSearch` adds the same criterion key (`"kmsKeyId"`) twice. Besides 
being redundant, reusing the same parameter name can lead to unexpected 
behavior depending on how `SearchBuilder` stores criteria. Drop the duplicate 
`and("kmsKeyId", ...)` entry and keep a single definition.



##########
tools/apidoc/gen_toc.py:
##########
@@ -56,6 +56,8 @@
     'HypervisorGuestOsNames': 'Guest OS',
     'Domain': 'Domain',
     'Template': 'Template',
+    'KMS': 'KMS',
+    'HSM': 'KMS',

Review Comment:
   In the TOC mapping, `'HSM': 'KMS'` appears to mislabel the HSM API section 
as KMS. This will likely group HSM docs under the wrong heading; set the 
display label for `HSM` to something like `'HSM'` / `'HSM Profiles'` instead.



##########
ui/src/views/compute/DeployVM.vue:
##########
@@ -1993,6 +2018,30 @@ export default {
       const param = this.params.networks
       this.fetchOptions(param, 'networks')
     },
+    fetchKmsKeys () {
+      if (!this.zoneId) {
+        return
+      }
+      this.loading.kmsKeys = true
+      this.options.kmsKeys = []
+      getAPI('listKMSKeys', {
+        zoneid: this.zoneId,
+        account: this.owner.account,
+        domainid: this.owner.domainid,
+        projectid: this.owner.projectid
+      }).then(response => {

Review Comment:
   `fetchKmsKeys()` calls `listKMSKeys` without a `purpose` filter, so 
non-volume keys (e.g., TLS purpose) may be shown in the Deploy VM wizard and 
selected for disk encryption. Pass `purpose: 'volume'` (matching Create Volume) 
to keep the UI aligned with volume-encryption usage and reduce server-side 
rejections.



##########
server/src/main/java/com/cloud/api/ApiResponseHelper.java:
##########
@@ -519,6 +520,8 @@ public class ApiResponseHelper implements ResponseGenerator 
{
     private ASNumberRangeDao asNumberRangeDao;
     @Inject
     private ASNumberDao asNumberDao;
+    @Inject
+    private HSMProfileDao hsmProfileDao;

Review Comment:
   `HSMProfileDao` is added as an injected dependency but isn't referenced 
anywhere in this class. If it's not needed for response generation, remove the 
unused injection (and import) to avoid dead code and potential style/check 
failures.



##########
server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java:
##########
@@ -284,6 +295,18 @@ public VolumeResponse newVolumeResponse(ResponseView view, 
VolumeJoinVO volume)
         volResponse.setObjectName("volume");
         volResponse.setExternalUuid(volume.getExternalUuid());
         volResponse.setEncryptionFormat(volume.getEncryptionFormat());
+        volResponse.setKmsKeyId(volume.getKmsKeyUuid());
+        volResponse.setKmsKey(volume.getKmsKeyName());
+
+        if (volume.getKmsWrappedKeyId() != null) {
+            KMSWrappedKeyVO wrappedKey = 
kmsWrappedKeyDao.findById(volume.getKmsWrappedKeyId());
+            if (wrappedKey != null) {
+                KMSKekVersionVO kekVersion = 
kmsKekVersionDao.findById(wrappedKey.getKekVersionId());
+                if (kekVersion != null) {
+                    
volResponse.setKmsKeyVersion(kekVersion.getVersionNumber());
+                }
+            }
+        }

Review Comment:
   `newVolumeResponse()` performs per-volume DAO lookups 
(`kmsWrappedKeyDao.findById` and then `kmsKekVersionDao.findById`) to compute 
`kmsKeyVersion`. This introduces an N+1 query pattern when listing many 
volumes. Consider extending `volume_view` to join the wrapped key and KEK 
version number (or batch-fetch these IDs once per response) to avoid per-row DB 
hits.



##########
server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java:
##########
@@ -58,6 +63,12 @@ public class VolumeJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<VolumeJo
     @Inject
     private PrimaryDataStoreDao primaryDataStoreDao;
     @Inject
+    private KMSKeyDao kmsKeyDao;
+    @Inject
+    private KMSWrappedKeyDao kmsWrappedKeyDao;
+    @Inject
+    private KMSKekVersionDao kmsKekVersionDao;

Review Comment:
   `kmsKeyDao` is injected but never used in this class. If it's not needed for 
the response, remove the injection to avoid confusion and keep the dependency 
surface minimal.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to