mirror of https://github.com/apache/cloudstack.git
temp commit
This commit is contained in:
parent
86e1d2b695
commit
6abcffa9d7
|
|
@ -867,6 +867,7 @@ public class ApiConstants {
|
|||
public static final String ITERATIONS = "iterations";
|
||||
public static final String SORT_BY = "sortby";
|
||||
public static final String CHANGE_CIDR = "changecidr";
|
||||
public static final String HSM_PROFILE = "hsmprofile";
|
||||
public static final String PURPOSE = "purpose";
|
||||
public static final String KMS_KEY_ID = "kmskeyid";
|
||||
public static final String KMS_KEY_VERSION = "kmskeyversion";
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ public class RotateKMSKeyCmd extends BaseAsyncCmd {
|
|||
description = "Key size for new KEK (default: same as current)")
|
||||
private Integer keyBits;
|
||||
|
||||
@Parameter(name = ApiConstants.HSM_PROFILE,
|
||||
type = CommandType.STRING,
|
||||
description = "The target HSM profile name for the new KEK version. If provided, migrates the key to this HSM.")
|
||||
private String hsmProfile;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -73,6 +78,10 @@ public class RotateKMSKeyCmd extends BaseAsyncCmd {
|
|||
return keyBits;
|
||||
}
|
||||
|
||||
public String getHsmProfile() {
|
||||
return hsmProfile;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -95,6 +95,11 @@ public class CreateKMSKeyCmd extends BaseCmd implements UserCmd {
|
|||
description = "Key size in bits: 128, 192, or 256 (default: 256)")
|
||||
private Integer keyBits;
|
||||
|
||||
@Parameter(name = ApiConstants.HSM_PROFILE,
|
||||
type = CommandType.STRING,
|
||||
description = "Name of HSM profile to create key in")
|
||||
private String hsmProfile;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -127,6 +132,10 @@ public class CreateKMSKeyCmd extends BaseCmd implements UserCmd {
|
|||
return keyBits != null ? keyBits : 256; // Default to 256 bits
|
||||
}
|
||||
|
||||
public String getHsmProfile() {
|
||||
return hsmProfile;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
// 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.api.command.user.kms.hsm;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.HSMProfile;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.NetworkRuleConflictException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import com.cloud.user.Account;
|
||||
|
||||
@APICommand(name = "addHSMProfile", description = "Adds a new HSM profile", responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.21.0")
|
||||
public class AddHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// API parameters
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "the name of the HSM profile")
|
||||
private String name;
|
||||
|
||||
@Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, required = true, description = "the protocol of the HSM profile (PKCS11, KMIP, etc.)")
|
||||
private String protocol;
|
||||
|
||||
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the zone ID where the HSM profile is available. If null, global scope (for admin only)")
|
||||
private Long zoneId;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "the domain ID where the HSM profile is available")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "the account ID of the HSM profile owner. If null, admin-provided (available to all accounts)")
|
||||
private Long accountId;
|
||||
|
||||
@Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, description = "the vendor name of the HSM")
|
||||
private String vendorName;
|
||||
|
||||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = true, description = "HSM configuration details (protocol specific)")
|
||||
private Map<String, String> details;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Accessors
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public Long getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getVendorName() {
|
||||
return vendorName;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Implementation
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
try {
|
||||
// Default to caller account if not admin and accountId not specified
|
||||
// But wait, the plan says: "No accountId parameter means account_id = NULL (admin-provided)"
|
||||
// However, regular users can add their own profiles.
|
||||
// So if caller is normal user, accountId should be forced to their account.
|
||||
|
||||
// Logic handled in KMSManagerImpl
|
||||
HSMProfile profile = kmsManager.addHSMProfile(this);
|
||||
HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
if (accountId != null) {
|
||||
return accountId;
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// 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.api.command.user.kms.hsm;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.HSMProfile;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.NetworkRuleConflictException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
|
||||
@APICommand(name = "deleteHSMProfile", description = "Deletes an HSM profile", responseObject = SuccessResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0")
|
||||
public class DeleteHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// API parameters
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true, description = "the ID of the HSM profile")
|
||||
private Long id;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Accessors
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Implementation
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
try {
|
||||
boolean result = kmsManager.deleteHSMProfile(this);
|
||||
if (result) {
|
||||
SuccessResponse response = new SuccessResponse(getCommandName());
|
||||
setResponseObject(response);
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete HSM profile");
|
||||
}
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
HSMProfile profile = _entityMgr.findById(HSMProfile.class, id);
|
||||
if (profile != null && profile.getAccountId() != null) {
|
||||
return profile.getAccountId();
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// 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.api.command.user.kms.hsm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseListCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.kms.HSMProfile;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
@APICommand(name = "listHSMProfiles", description = "Lists HSM profiles", responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, since = "4.21.0")
|
||||
public class ListHSMProfilesCmd extends BaseListCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// API parameters
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the zone ID")
|
||||
private Long zoneId;
|
||||
|
||||
@Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, description = "the protocol of the HSM profile")
|
||||
private String protocol;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "list only enabled profiles")
|
||||
private Boolean enabled;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Accessors
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Implementation
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
List<HSMProfile> profiles = kmsManager.listHSMProfiles(this);
|
||||
ListResponse<HSMProfileResponse> response = new ListResponse<>();
|
||||
List<HSMProfileResponse> profileResponses = new ArrayList<>();
|
||||
|
||||
for (HSMProfile profile : profiles) {
|
||||
HSMProfileResponse profileResponse = kmsManager.createHSMProfileResponse(profile);
|
||||
profileResponses.add(profileResponse);
|
||||
}
|
||||
|
||||
response.setResponses(profileResponses);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// 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.api.command.user.kms.hsm;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.HSMProfile;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.NetworkRuleConflictException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
|
||||
@APICommand(name = "updateHSMProfile", description = "Updates an HSM profile", responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.21.0")
|
||||
public class UpdateHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// API parameters
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true, description = "the ID of the HSM profile")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the HSM profile")
|
||||
private String name;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "whether the HSM profile is enabled")
|
||||
private Boolean enabled;
|
||||
|
||||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "HSM configuration details to update (protocol specific)")
|
||||
private Map<String, String> details;
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Accessors
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////=====
|
||||
// Implementation
|
||||
////////////////////////////////////////////////=====
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
try {
|
||||
HSMProfile profile = kmsManager.updateHSMProfile(this);
|
||||
HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
HSMProfile profile = _entityMgr.findById(HSMProfile.class, id);
|
||||
if (profile != null && profile.getAccountId() != null) {
|
||||
return profile.getAccountId();
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
// 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.api.response;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.kms.HSMProfile;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@EntityReference(value = HSMProfile.class)
|
||||
public class HSMProfileResponse extends BaseResponse {
|
||||
@SerializedName(ApiConstants.ID)
|
||||
@Param(description = "the ID of the HSM profile")
|
||||
private String id;
|
||||
|
||||
@SerializedName(ApiConstants.NAME)
|
||||
@Param(description = "the name of the HSM profile")
|
||||
private String name;
|
||||
|
||||
@SerializedName(ApiConstants.PROTOCOL)
|
||||
@Param(description = "the protocol of the HSM profile")
|
||||
private String protocol;
|
||||
|
||||
@SerializedName(ApiConstants.ACCOUNT_ID)
|
||||
@Param(description = "the account ID of the HSM profile owner")
|
||||
private String accountId;
|
||||
|
||||
@SerializedName(ApiConstants.ACCOUNT)
|
||||
@Param(description = "the account name of the HSM profile owner")
|
||||
private String accountName;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN_ID)
|
||||
@Param(description = "the domain ID of the HSM profile owner")
|
||||
private String domainId;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN)
|
||||
@Param(description = "the domain name of the HSM profile owner")
|
||||
private String domainName;
|
||||
|
||||
@SerializedName(ApiConstants.ZONE_ID)
|
||||
@Param(description = "the zone ID where the HSM profile is available")
|
||||
private String zoneId;
|
||||
|
||||
@SerializedName(ApiConstants.ZONE_NAME)
|
||||
@Param(description = "the zone name where the HSM profile is available")
|
||||
private String zoneName;
|
||||
|
||||
@SerializedName("vendor")
|
||||
@Param(description = "the vendor name of the HSM profile")
|
||||
private String vendorName;
|
||||
|
||||
@SerializedName(ApiConstants.STATE)
|
||||
@Param(description = "the state of the HSM profile")
|
||||
private String state;
|
||||
|
||||
@SerializedName(ApiConstants.ENABLED)
|
||||
@Param(description = "whether the HSM profile is enabled")
|
||||
private Boolean enabled;
|
||||
|
||||
@SerializedName(ApiConstants.CREATED)
|
||||
@Param(description = "the date the HSM profile was created")
|
||||
private Date created;
|
||||
|
||||
@SerializedName(ApiConstants.DETAILS)
|
||||
@Param(description = "HSM configuration details (sensitive values are encrypted)")
|
||||
private Map<String, String> details;
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public void setAccountId(String accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
public void setAccountName(String accountName) {
|
||||
this.accountName = accountName;
|
||||
}
|
||||
|
||||
public void setDomainId(String domainId) {
|
||||
this.domainId = domainId;
|
||||
}
|
||||
|
||||
public void setDomainName(String domainName) {
|
||||
this.domainName = domainName;
|
||||
}
|
||||
|
||||
public void setZoneId(String zoneId) {
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public void setZoneName(String zoneName) {
|
||||
this.zoneName = zoneName;
|
||||
}
|
||||
|
||||
public void setVendorName(String vendorName) {
|
||||
this.vendorName = vendorName;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public void setDetails(Map<String, String> details) {
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// 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;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
public interface HSMProfile extends Identity, InternalIdentity {
|
||||
String getName();
|
||||
|
||||
String getProtocol();
|
||||
|
||||
Long getAccountId();
|
||||
|
||||
Long getDomainId();
|
||||
|
||||
Long getZoneId();
|
||||
|
||||
String getVendorName();
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
Date getCreated();
|
||||
|
||||
Date getRemoved();
|
||||
}
|
||||
|
|
@ -26,6 +26,11 @@ import org.apache.cloudstack.api.command.user.kms.CreateKMSKeyCmd;
|
|||
import org.apache.cloudstack.api.command.user.kms.DeleteKMSKeyCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.ListKMSKeysCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.UpdateKMSKeyCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.AddHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.DeleteHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.ListHSMProfilesCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.UpdateHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
|
|
@ -331,4 +336,49 @@ public interface KMSManager extends Manager, Configurable {
|
|||
* @return true if all keys were successfully deleted
|
||||
*/
|
||||
boolean deleteKMSKeysByAccountId(Long accountId);
|
||||
|
||||
// ==================== HSM Profile Management ====================
|
||||
|
||||
/**
|
||||
* Add a new HSM profile
|
||||
*
|
||||
* @param cmd the add command
|
||||
* @return the created HSM profile
|
||||
* @throws KMSException if addition fails
|
||||
*/
|
||||
HSMProfile addHSMProfile(AddHSMProfileCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* List HSM profiles
|
||||
*
|
||||
* @param cmd the list command
|
||||
* @return list of HSM profiles
|
||||
*/
|
||||
List<HSMProfile> listHSMProfiles(ListHSMProfilesCmd cmd);
|
||||
|
||||
/**
|
||||
* Delete an HSM profile
|
||||
*
|
||||
* @param cmd the delete command
|
||||
* @return true if deletion was successful
|
||||
* @throws KMSException if deletion fails
|
||||
*/
|
||||
boolean deleteHSMProfile(DeleteHSMProfileCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* Update an HSM profile
|
||||
*
|
||||
* @param cmd the update command
|
||||
* @return the updated HSM profile
|
||||
* @throws KMSException if update fails
|
||||
*/
|
||||
HSMProfile updateHSMProfile(UpdateHSMProfileCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* Create a response object for an HSM profile
|
||||
*
|
||||
* @param profile the HSM profile
|
||||
* @return the response object
|
||||
*/
|
||||
HSMProfileResponse createHSMProfileResponse(HSMProfile profile);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
// 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;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.apache.cloudstack.api.ResourceDetail;
|
||||
|
||||
@Entity
|
||||
@Table(name = "kms_hsm_profile_details")
|
||||
public class HSMProfileDetailsVO implements ResourceDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private long id;
|
||||
|
||||
@Column(name = "profile_id")
|
||||
private long resourceId;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
@Column(name = "value")
|
||||
private String value;
|
||||
|
||||
public HSMProfileDetailsVO() {
|
||||
}
|
||||
|
||||
public HSMProfileDetailsVO(long profileId, String name, String value) {
|
||||
this.resourceId = profileId;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplay() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
// 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;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "kms_hsm_profiles")
|
||||
public class HSMProfileVO implements HSMProfile {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private long id;
|
||||
|
||||
@Column(name = "uuid")
|
||||
private String uuid;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
@Column(name = "protocol")
|
||||
private String protocol;
|
||||
|
||||
@Column(name = "account_id")
|
||||
private Long accountId;
|
||||
|
||||
@Column(name = "domain_id")
|
||||
private Long domainId;
|
||||
|
||||
@Column(name = "zone_id")
|
||||
private Long zoneId;
|
||||
|
||||
@Column(name = "vendor_name")
|
||||
private String vendorName;
|
||||
|
||||
@Column(name = "enabled")
|
||||
private boolean enabled;
|
||||
|
||||
@Column(name = "created")
|
||||
private Date created;
|
||||
|
||||
@Column(name = "removed")
|
||||
private Date removed;
|
||||
|
||||
public HSMProfileVO() {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.created = new Date();
|
||||
}
|
||||
|
||||
public HSMProfileVO(String name, String protocol, Long accountId, Long domainId, Long zoneId, String vendorName) {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.name = name;
|
||||
this.protocol = protocol;
|
||||
this.accountId = accountId;
|
||||
this.domainId = domainId;
|
||||
this.zoneId = zoneId;
|
||||
this.vendorName = vendorName;
|
||||
this.enabled = true;
|
||||
this.created = new Date();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVendorName() {
|
||||
return vendorName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getRemoved() {
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setVendorName(String vendorName) {
|
||||
this.vendorName = vendorName;
|
||||
}
|
||||
|
||||
public void setRemoved(Date removed) {
|
||||
this.removed = removed;
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +64,12 @@ public class KMSKekVersionVO {
|
|||
@Enumerated(EnumType.STRING)
|
||||
private Status status;
|
||||
|
||||
@Column(name = "hsm_profile_id")
|
||||
private Long hsmProfileId;
|
||||
|
||||
@Column(name = "hsm_key_label")
|
||||
private String hsmKeyLabel;
|
||||
|
||||
@Column(name = GenericDao.CREATED_COLUMN, nullable = false)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
|
@ -160,6 +166,22 @@ public class KMSKekVersionVO {
|
|||
this.status = status;
|
||||
}
|
||||
|
||||
public Long getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
||||
public void setHsmProfileId(Long hsmProfileId) {
|
||||
this.hsmProfileId = hsmProfileId;
|
||||
}
|
||||
|
||||
public String getHsmKeyLabel() {
|
||||
return hsmKeyLabel;
|
||||
}
|
||||
|
||||
public void setHsmKeyLabel(String hsmKeyLabel) {
|
||||
this.hsmKeyLabel = hsmKeyLabel;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ public class KMSKeyVO implements KMSKey {
|
|||
@Enumerated(EnumType.STRING)
|
||||
private State state;
|
||||
|
||||
@Column(name = "hsm_profile_id")
|
||||
private Long hsmProfileId;
|
||||
|
||||
@Column(name = GenericDao.CREATED_COLUMN, nullable = false)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
|
@ -249,6 +252,14 @@ public class KMSKeyVO implements KMSKey {
|
|||
this.state = state;
|
||||
}
|
||||
|
||||
public Long getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
||||
public void setHsmProfileId(Long hsmProfileId) {
|
||||
this.hsmProfileId = hsmProfileId;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
// 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 java.util.List;
|
||||
|
||||
import org.apache.cloudstack.kms.HSMProfileVO;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
|
||||
public interface HSMProfileDao extends GenericDao<HSMProfileVO, Long> {
|
||||
List<HSMProfileVO> listByAccountId(Long accountId);
|
||||
List<HSMProfileVO> listAdminProfiles();
|
||||
List<HSMProfileVO> listAdminProfiles(Long zoneId);
|
||||
HSMProfileVO findByName(String name);
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// 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 java.util.List;
|
||||
|
||||
import org.apache.cloudstack.kms.HSMProfileVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.SearchCriteria.Op;
|
||||
|
||||
@Component
|
||||
public class HSMProfileDaoImpl extends GenericDaoBase<HSMProfileVO, Long> implements HSMProfileDao {
|
||||
|
||||
protected SearchBuilder<HSMProfileVO> AccountSearch;
|
||||
protected SearchBuilder<HSMProfileVO> AdminSearch;
|
||||
protected SearchBuilder<HSMProfileVO> NameSearch;
|
||||
|
||||
public HSMProfileDaoImpl() {
|
||||
super();
|
||||
|
||||
AccountSearch = createSearchBuilder();
|
||||
AccountSearch.and("accountId", AccountSearch.entity().getAccountId(), Op.EQ);
|
||||
AccountSearch.and("removed", AccountSearch.entity().getRemoved(), Op.NULL);
|
||||
AccountSearch.done();
|
||||
|
||||
AdminSearch = createSearchBuilder();
|
||||
AdminSearch.and("accountId", AdminSearch.entity().getAccountId(), Op.NULL);
|
||||
AdminSearch.and("zoneId", AdminSearch.entity().getZoneId(), Op.EQ);
|
||||
AdminSearch.and("removed", AdminSearch.entity().getRemoved(), Op.NULL);
|
||||
AdminSearch.done();
|
||||
|
||||
NameSearch = createSearchBuilder();
|
||||
NameSearch.and("name", NameSearch.entity().getName(), Op.EQ);
|
||||
NameSearch.and("removed", NameSearch.entity().getRemoved(), Op.NULL);
|
||||
NameSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HSMProfileVO> listByAccountId(Long accountId) {
|
||||
SearchCriteria<HSMProfileVO> sc = AccountSearch.create();
|
||||
sc.setParameters("accountId", accountId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HSMProfileVO> listAdminProfiles() {
|
||||
SearchCriteria<HSMProfileVO> sc = AdminSearch.create();
|
||||
// Global admin profiles have zone_id = NULL
|
||||
sc.setParameters("zoneId", (Object)null);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HSMProfileVO> listAdminProfiles(Long zoneId) {
|
||||
SearchCriteria<HSMProfileVO> sc = AdminSearch.create();
|
||||
sc.setParameters("zoneId", zoneId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HSMProfileVO findByName(String name) {
|
||||
SearchCriteria<HSMProfileVO> sc = NameSearch.create();
|
||||
sc.setParameters("name", name);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// 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 java.util.List;
|
||||
|
||||
import org.apache.cloudstack.kms.HSMProfileDetailsVO;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
|
||||
public interface HSMProfileDetailsDao extends GenericDao<HSMProfileDetailsVO, Long> {
|
||||
List<HSMProfileDetailsVO> listByProfileId(long profileId);
|
||||
void persist(long profileId, String name, String value);
|
||||
HSMProfileDetailsVO findDetail(long profileId, String name);
|
||||
void deleteDetails(long profileId);
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// 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 java.util.List;
|
||||
|
||||
import org.apache.cloudstack.kms.HSMProfileDetailsVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.SearchCriteria.Op;
|
||||
|
||||
@Component
|
||||
public class HSMProfileDetailsDaoImpl extends GenericDaoBase<HSMProfileDetailsVO, Long> implements HSMProfileDetailsDao {
|
||||
|
||||
protected SearchBuilder<HSMProfileDetailsVO> ProfileSearch;
|
||||
protected SearchBuilder<HSMProfileDetailsVO> DetailSearch;
|
||||
|
||||
public HSMProfileDetailsDaoImpl() {
|
||||
super();
|
||||
|
||||
ProfileSearch = createSearchBuilder();
|
||||
ProfileSearch.and("profileId", ProfileSearch.entity().getResourceId(), Op.EQ);
|
||||
ProfileSearch.done();
|
||||
|
||||
DetailSearch = createSearchBuilder();
|
||||
DetailSearch.and("profileId", DetailSearch.entity().getResourceId(), Op.EQ);
|
||||
DetailSearch.and("name", DetailSearch.entity().getName(), Op.EQ);
|
||||
DetailSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HSMProfileDetailsVO> listByProfileId(long profileId) {
|
||||
SearchCriteria<HSMProfileDetailsVO> sc = ProfileSearch.create();
|
||||
sc.setParameters("profileId", profileId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void persist(long profileId, String name, String value) {
|
||||
HSMProfileDetailsVO vo = new HSMProfileDetailsVO(profileId, name, value);
|
||||
persist(vo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HSMProfileDetailsVO findDetail(long profileId, String name) {
|
||||
SearchCriteria<HSMProfileDetailsVO> sc = DetailSearch.create();
|
||||
sc.setParameters("profileId", profileId);
|
||||
sc.setParameters("name", name);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDetails(long profileId) {
|
||||
SearchCriteria<HSMProfileDetailsVO> sc = ProfileSearch.create();
|
||||
sc.setParameters("profileId", profileId);
|
||||
remove(sc);
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,6 @@ CREATE TABLE IF NOT EXISTS `cloud`.`webhook_filter` (
|
|||
CONSTRAINT `fk_webhook_filter__webhook_id` FOREIGN KEY(`webhook_id`) REFERENCES `webhook`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
|
||||
-- "api_keypair" table for API and secret keys
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`api_keypair` (
|
||||
`id` bigint(20) unsigned NOT NULL auto_increment,
|
||||
|
|
@ -116,6 +115,46 @@ CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'deleteUserKey
|
|||
-- Add conserve mode for VPC offerings
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tinyint(1) unsigned NULL DEFAULT 0 COMMENT ''True if the VPC offering is IP conserve mode enabled, allowing public IP services to be used across multiple VPC tiers'' ');
|
||||
|
||||
-- KMS HSM Profiles (Generic table for PKCS#11, KMIP, etc.)
|
||||
-- Scoped to account (user-provided) or global/zone (admin-provided)
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profiles` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`uuid` VARCHAR(40) NOT NULL,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`protocol` VARCHAR(32) NOT NULL COMMENT 'PKCS11, KMIP, AWS_KMS, etc.',
|
||||
|
||||
-- Scoping
|
||||
`account_id` BIGINT UNSIGNED COMMENT 'null = admin-provided (available to all accounts)',
|
||||
`domain_id` BIGINT UNSIGNED COMMENT 'null = zone/global scope',
|
||||
`zone_id` BIGINT UNSIGNED COMMENT 'null = global scope',
|
||||
|
||||
-- Metadata
|
||||
`vendor_name` VARCHAR(64) COMMENT 'HSM vendor (Thales, AWS, SoftHSM, etc.)',
|
||||
`enabled` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`created` DATETIME NOT NULL,
|
||||
`removed` DATETIME,
|
||||
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_uuid` (`uuid`),
|
||||
UNIQUE KEY `uk_account_name` (`account_id`, `name`, `removed`),
|
||||
INDEX `idx_protocol_enabled` (`protocol`, `enabled`, `removed`),
|
||||
INDEX `idx_scoping` (`account_id`, `domain_id`, `zone_id`, `removed`),
|
||||
CONSTRAINT `fk_kms_hsm_profiles__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_hsm_profiles__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_hsm_profiles__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='HSM profiles for KMS providers';
|
||||
|
||||
-- KMS HSM Profile Details (Protocol-specific configuration)
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profile_details` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`profile_id` BIGINT UNSIGNED NOT NULL COMMENT 'HSM profile ID',
|
||||
`name` VARCHAR(255) NOT NULL COMMENT 'Config key (e.g. library_path, endpoint, pin, cert_content)',
|
||||
`value` TEXT NOT NULL COMMENT 'Config value (encrypted if sensitive)',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_profile_name` (`profile_id`, `name`),
|
||||
CONSTRAINT `fk_kms_hsm_profile_details__profile_id` FOREIGN KEY (`profile_id`) REFERENCES `kms_hsm_profiles`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Details for HSM profiles (key-value configuration)';
|
||||
|
||||
-- KMS Keys (Key Encryption Key Metadata)
|
||||
-- Account-scoped KEKs for envelope encryption
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`kms_keys` (
|
||||
|
|
@ -132,6 +171,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_keys` (
|
|||
`algorithm` VARCHAR(64) NOT NULL DEFAULT 'AES/GCM/NoPadding' COMMENT 'Encryption algorithm',
|
||||
`key_bits` INT NOT NULL DEFAULT 256 COMMENT 'Key size in bits',
|
||||
`state` VARCHAR(32) NOT NULL DEFAULT 'Enabled' COMMENT 'Enabled, Disabled, or Deleted',
|
||||
`hsm_profile_id` BIGINT UNSIGNED COMMENT 'Current HSM profile ID for this key',
|
||||
`created` DATETIME NOT NULL COMMENT 'Creation timestamp',
|
||||
`removed` DATETIME COMMENT 'Removal timestamp for soft delete',
|
||||
PRIMARY KEY (`id`),
|
||||
|
|
@ -142,7 +182,8 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_keys` (
|
|||
INDEX `idx_kek_label_provider` (`kek_label`, `provider_name`),
|
||||
CONSTRAINT `fk_kms_keys__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_keys__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_keys__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE
|
||||
CONSTRAINT `fk_kms_keys__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_keys__hsm_profile_id` FOREIGN KEY (`hsm_profile_id`) REFERENCES `kms_hsm_profiles`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KMS Key (KEK) metadata - account-scoped keys for envelope encryption';
|
||||
|
||||
-- KMS KEK Versions (multiple KEKs per KMS key for gradual rotation)
|
||||
|
|
@ -154,6 +195,8 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_kek_versions` (
|
|||
`version_number` INT NOT NULL COMMENT 'Version number (1, 2, 3, ...)',
|
||||
`kek_label` VARCHAR(255) NOT NULL COMMENT 'Provider-specific KEK label/ID for this version',
|
||||
`status` VARCHAR(32) NOT NULL DEFAULT 'Active' COMMENT 'Active, Previous, Archived',
|
||||
`hsm_profile_id` BIGINT UNSIGNED COMMENT 'HSM profile where this KEK version is stored',
|
||||
`hsm_key_label` VARCHAR(255) COMMENT 'Optional HSM-specific key label/alias',
|
||||
`created` DATETIME NOT NULL COMMENT 'Creation timestamp',
|
||||
`removed` DATETIME COMMENT 'Removal timestamp for soft delete',
|
||||
PRIMARY KEY (`id`),
|
||||
|
|
@ -161,7 +204,8 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_kek_versions` (
|
|||
UNIQUE KEY `uk_kms_key_version` (`kms_key_id`, `version_number`, `removed`),
|
||||
INDEX `idx_kms_key_status` (`kms_key_id`, `status`, `removed`),
|
||||
INDEX `idx_kek_label` (`kek_label`),
|
||||
CONSTRAINT `fk_kms_kek_versions__kms_key_id` FOREIGN KEY (`kms_key_id`) REFERENCES `kms_keys`(`id`) ON DELETE CASCADE
|
||||
CONSTRAINT `fk_kms_kek_versions__kms_key_id` FOREIGN KEY (`kms_key_id`) REFERENCES `kms_keys`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_kek_versions__hsm_profile_id` FOREIGN KEY (`hsm_profile_id`) REFERENCES `kms_hsm_profiles`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KEK versions for a KMS key - supports gradual rotation';
|
||||
|
||||
-- KMS Wrapped Keys (Data Encryption Keys)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,20 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
// ==================== KEK Management ====================
|
||||
|
||||
/**
|
||||
* Create a new Key Encryption Key (KEK) in the secure backend
|
||||
* Create a new Key Encryption Key (KEK) in the secure backend with explicit HSM profile.
|
||||
*
|
||||
* @param purpose the purpose/scope for this KEK
|
||||
* @param label human-readable label for the KEK (must be unique within purpose)
|
||||
* @param keyBits key size in bits (typically 128, 192, or 256)
|
||||
* @param hsmProfileId optional HSM profile ID to create the KEK in (null for auto-resolution/default)
|
||||
* @return the KEK identifier (label or handle) for later reference
|
||||
* @throws KMSException if KEK creation fails
|
||||
*/
|
||||
String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Create a new Key Encryption Key (KEK) in the secure backend.
|
||||
* Delegates to {@link #createKek(KeyPurpose, String, int, Long)} with null profile ID.
|
||||
*
|
||||
* @param purpose the purpose/scope for this KEK
|
||||
* @param label human-readable label for the KEK (must be unique within purpose)
|
||||
|
|
@ -57,7 +70,9 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
* @return the KEK identifier (label or handle) for later reference
|
||||
* @throws KMSException if KEK creation fails
|
||||
*/
|
||||
String createKek(KeyPurpose purpose, String label, int keyBits) throws KMSException;
|
||||
default String createKek(KeyPurpose purpose, String label, int keyBits) throws KMSException {
|
||||
return createKek(purpose, label, keyBits, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a KEK from the secure backend.
|
||||
|
|
@ -89,7 +104,20 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
// ==================== DEK Operations ====================
|
||||
|
||||
/**
|
||||
* Wrap (encrypt) a plaintext Data Encryption Key with a KEK
|
||||
* Wrap (encrypt) a plaintext Data Encryption Key with a KEK using explicit HSM profile.
|
||||
*
|
||||
* @param plainDek the plaintext DEK to wrap (caller must zeroize after call)
|
||||
* @param purpose the intended purpose of this DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return WrappedKey containing the encrypted DEK and metadata
|
||||
* @throws KMSException if wrapping fails or KEK not found
|
||||
*/
|
||||
WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Wrap (encrypt) a plaintext Data Encryption Key with a KEK.
|
||||
* Delegates to {@link #wrapKey(byte[], KeyPurpose, String, Long)} with null profile ID.
|
||||
*
|
||||
* @param plainDek the plaintext DEK to wrap (caller must zeroize after call)
|
||||
* @param purpose the intended purpose of this DEK
|
||||
|
|
@ -97,10 +125,25 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
* @return WrappedKey containing the encrypted DEK and metadata
|
||||
* @throws KMSException if wrapping fails or KEK not found
|
||||
*/
|
||||
WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel) throws KMSException;
|
||||
default WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel) throws KMSException {
|
||||
return wrapKey(plainDek, purpose, kekLabel, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap (decrypt) a wrapped DEK to obtain the plaintext key
|
||||
* Unwrap (decrypt) a wrapped DEK to obtain the plaintext key using explicit HSM profile.
|
||||
* <p>
|
||||
* SECURITY: Caller MUST zeroize the returned byte array after use
|
||||
*
|
||||
* @param wrappedKey the wrapped key to decrypt
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return plaintext DEK (caller must zeroize!)
|
||||
* @throws KMSException if unwrapping fails or KEK not found
|
||||
*/
|
||||
byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Unwrap (decrypt) a wrapped DEK to obtain the plaintext key.
|
||||
* Delegates to {@link #unwrapKey(WrappedKey, Long)} with null profile ID.
|
||||
* <p>
|
||||
* SECURITY: Caller MUST zeroize the returned byte array after use
|
||||
*
|
||||
|
|
@ -108,10 +151,26 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
* @return plaintext DEK (caller must zeroize!)
|
||||
* @throws KMSException if unwrapping fails or KEK not found
|
||||
*/
|
||||
byte[] unwrapKey(WrappedKey wrappedKey) throws KMSException;
|
||||
default byte[] unwrapKey(WrappedKey wrappedKey) throws KMSException {
|
||||
return unwrapKey(wrappedKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new random DEK and immediately wrap it with a KEK
|
||||
* Generate a new random DEK and immediately wrap it with a KEK using explicit HSM profile.
|
||||
* (convenience method combining generation + wrapping)
|
||||
*
|
||||
* @param purpose the intended purpose of the new DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @param keyBits DEK size in bits (typically 128, 192, or 256)
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return WrappedKey containing the newly generated and wrapped DEK
|
||||
* @throws KMSException if generation or wrapping fails
|
||||
*/
|
||||
WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Generate a new random DEK and immediately wrap it with a KEK.
|
||||
* Delegates to {@link #generateAndWrapDek(KeyPurpose, String, int, Long)} with null profile ID.
|
||||
* (convenience method combining generation + wrapping)
|
||||
*
|
||||
* @param purpose the intended purpose of the new DEK
|
||||
|
|
@ -120,10 +179,25 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
* @return WrappedKey containing the newly generated and wrapped DEK
|
||||
* @throws KMSException if generation or wrapping fails
|
||||
*/
|
||||
WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits) throws KMSException;
|
||||
default WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits) throws KMSException {
|
||||
return generateAndWrapDek(purpose, kekLabel, keyBits, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrap a DEK with a different KEK (used during key rotation) using explicit target HSM profile.
|
||||
* This unwraps with the old KEK and wraps with the new KEK without exposing the plaintext DEK.
|
||||
*
|
||||
* @param oldWrappedKey the currently wrapped key
|
||||
* @param newKekLabel the label of the new KEK to wrap with
|
||||
* @param targetHsmProfileId optional target HSM profile ID to wrap with (null for auto-resolution/default)
|
||||
* @return new WrappedKey encrypted with the new KEK
|
||||
* @throws KMSException if rewrapping fails
|
||||
*/
|
||||
WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel, Long targetHsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Rewrap a DEK with a different KEK (used during key rotation).
|
||||
* Delegates to {@link #rewrapKey(WrappedKey, String, Long)} with null profile ID.
|
||||
* This unwraps with the old KEK and wraps with the new KEK without exposing the plaintext DEK.
|
||||
*
|
||||
* @param oldWrappedKey the currently wrapped key
|
||||
|
|
@ -131,7 +205,9 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
* @return new WrappedKey encrypted with the new KEK
|
||||
* @throws KMSException if rewrapping fails
|
||||
*/
|
||||
WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel) throws KMSException;
|
||||
default WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel) throws KMSException {
|
||||
return rewrapKey(oldWrappedKey, newKekLabel, null);
|
||||
}
|
||||
|
||||
// ==================== Health & Status ====================
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,12 @@ public class DatabaseKMSProvider extends AdapterBase implements KMSProvider {
|
|||
return PROVIDER_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmProfileId) throws KMSException {
|
||||
// Database provider ignores hsmProfileId
|
||||
return createKek(purpose, label, keyBits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createKek(KeyPurpose purpose, String label, int keyBits) throws KMSException {
|
||||
if (keyBits != 128 && keyBits != 192 && keyBits != 256) {
|
||||
|
|
@ -213,6 +219,12 @@ public class DatabaseKMSProvider extends AdapterBase implements KMSProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey wrapKey(byte[] plainKey, KeyPurpose purpose, String kekLabel, Long hsmProfileId) throws KMSException {
|
||||
// Database provider ignores hsmProfileId
|
||||
return wrapKey(plainKey, purpose, kekLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey wrapKey(byte[] plainKey, KeyPurpose purpose, String kekLabel) throws KMSException {
|
||||
if (plainKey == null || plainKey.length == 0) {
|
||||
|
|
@ -241,6 +253,12 @@ public class DatabaseKMSProvider extends AdapterBase implements KMSProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSException {
|
||||
// Database provider ignores hsmProfileId
|
||||
return unwrapKey(wrappedKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] unwrapKey(WrappedKey wrappedKey) throws KMSException {
|
||||
if (wrappedKey == null) {
|
||||
|
|
@ -276,6 +294,12 @@ public class DatabaseKMSProvider extends AdapterBase implements KMSProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits, Long hsmProfileId) throws KMSException {
|
||||
// Database provider ignores hsmProfileId
|
||||
return generateAndWrapDek(purpose, kekLabel, keyBits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits) throws KMSException {
|
||||
if (keyBits != 128 && keyBits != 192 && keyBits != 256) {
|
||||
|
|
@ -294,6 +318,12 @@ public class DatabaseKMSProvider extends AdapterBase implements KMSProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel, Long targetHsmProfileId) throws KMSException {
|
||||
// Database provider ignores targetHsmProfileId
|
||||
return rewrapKey(oldWrappedKey, newKekLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel) throws KMSException {
|
||||
// Unwrap with old KEK
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloud-plugin-kms-pkcs11</artifactId>
|
||||
<name>Apache CloudStack Plugin - KMS PKCS#11 Provider</name>
|
||||
<description>PKCS#11-backed KMS provider for HSM integration</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-kms-plugins</artifactId>
|
||||
<version>4.23.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-kms</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-config</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-utils</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-engine-schema</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
// 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.provider.pkcs11;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.framework.kms.KMSProvider;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
import org.apache.cloudstack.framework.kms.WrappedKey;
|
||||
import org.apache.cloudstack.kms.HSMProfileDetailsVO;
|
||||
import org.apache.cloudstack.kms.KMSKekVersionVO;
|
||||
import org.apache.cloudstack.kms.dao.HSMProfileDao;
|
||||
import org.apache.cloudstack.kms.dao.HSMProfileDetailsDao;
|
||||
import org.apache.cloudstack.kms.dao.KMSKekVersionDao;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.crypt.DBEncryptionUtil;
|
||||
|
||||
@Component
|
||||
public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
||||
private static final Logger logger = LogManager.getLogger(PKCS11HSMProvider.class);
|
||||
private static final String PROVIDER_NAME = "pkcs11";
|
||||
|
||||
@Inject
|
||||
private HSMProfileDao hsmProfileDao;
|
||||
|
||||
@Inject
|
||||
private HSMProfileDetailsDao hsmProfileDetailsDao;
|
||||
|
||||
@Inject
|
||||
private KMSKekVersionDao kmsKekVersionDao;
|
||||
|
||||
// Session pool per HSM profile
|
||||
private final Map<Long, HSMSessionPool> sessionPools = new ConcurrentHashMap<>();
|
||||
|
||||
// Profile configuration caching
|
||||
private final Map<Long, Map<String, String>> profileConfigCache = new ConcurrentHashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
logger.info("Initializing PKCS11HSMProvider");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderName() {
|
||||
return PROVIDER_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmProfileId) throws KMSException {
|
||||
if (hsmProfileId == null) {
|
||||
throw KMSException.invalidParameter("HSM Profile ID is required for PKCS#11 provider");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(label)) {
|
||||
label = generateKekLabel(purpose);
|
||||
}
|
||||
|
||||
HSMSessionPool pool = getSessionPool(hsmProfileId);
|
||||
PKCS11Session session = null;
|
||||
try {
|
||||
session = pool.acquireSession(5000);
|
||||
return session.generateKey(label, keyBits, purpose);
|
||||
} finally {
|
||||
pool.releaseSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel, Long hsmProfileId) throws KMSException {
|
||||
if (hsmProfileId == null) {
|
||||
hsmProfileId = resolveProfileId(kekLabel);
|
||||
}
|
||||
|
||||
HSMSessionPool pool = getSessionPool(hsmProfileId);
|
||||
PKCS11Session session = null;
|
||||
try {
|
||||
session = pool.acquireSession(5000);
|
||||
byte[] wrappedBlob = session.wrapKey(plainDek, kekLabel);
|
||||
return new WrappedKey(kekLabel, purpose, "AES/GCM/NoPadding", wrappedBlob, PROVIDER_NAME, new Date(), null);
|
||||
} finally {
|
||||
pool.releaseSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSException {
|
||||
if (hsmProfileId == null) {
|
||||
hsmProfileId = resolveProfileId(wrappedKey.getKekId());
|
||||
}
|
||||
|
||||
HSMSessionPool pool = getSessionPool(hsmProfileId);
|
||||
PKCS11Session session = null;
|
||||
try {
|
||||
session = pool.acquireSession(5000);
|
||||
return session.unwrapKey(wrappedKey.getWrappedKeyMaterial(), wrappedKey.getKekId());
|
||||
} finally {
|
||||
pool.releaseSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel, Long targetHsmProfileId) throws KMSException {
|
||||
// 1. Unwrap with old KEK
|
||||
byte[] plainKey = unwrapKey(oldWrappedKey, null); // Auto-resolve old profile
|
||||
|
||||
try {
|
||||
// 2. Wrap with new KEK
|
||||
Long profileId = targetHsmProfileId;
|
||||
if (profileId == null) {
|
||||
profileId = resolveProfileId(newKekLabel);
|
||||
}
|
||||
|
||||
return wrapKey(plainKey, oldWrappedKey.getPurpose(), newKekLabel, profileId);
|
||||
} finally {
|
||||
// Zeroize plaintext key
|
||||
java.util.Arrays.fill(plainKey, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits, Long hsmProfileId) throws KMSException {
|
||||
// Generate random DEK
|
||||
byte[] dekBytes = new byte[keyBits / 8];
|
||||
new java.security.SecureRandom().nextBytes(dekBytes);
|
||||
|
||||
try {
|
||||
return wrapKey(dekBytes, purpose, kekLabel, hsmProfileId);
|
||||
} finally {
|
||||
java.util.Arrays.fill(dekBytes, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteKek(String kekId) throws KMSException {
|
||||
Long hsmProfileId = resolveProfileId(kekId);
|
||||
HSMSessionPool pool = getSessionPool(hsmProfileId);
|
||||
PKCS11Session session = null;
|
||||
try {
|
||||
session = pool.acquireSession(5000);
|
||||
session.deleteKey(kekId);
|
||||
} finally {
|
||||
pool.releaseSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listKeks(KeyPurpose purpose) throws KMSException {
|
||||
throw new KMSException(KMSException.ErrorType.OPERATION_FAILED, "Listing KEKs directly from HSMs not supported, use DB");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKekAvailable(String kekId) throws KMSException {
|
||||
Long hsmProfileId = resolveProfileId(kekId);
|
||||
if (hsmProfileId == null) return false;
|
||||
|
||||
HSMSessionPool pool = getSessionPool(hsmProfileId);
|
||||
PKCS11Session session = null;
|
||||
try {
|
||||
session = pool.acquireSession(5000);
|
||||
return session.checkKeyExists(kekId);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} finally {
|
||||
pool.releaseSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean healthCheck() throws KMSException {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Long resolveProfileId(String kekLabel) throws KMSException {
|
||||
KMSKekVersionVO version = kmsKekVersionDao.findByKekLabel(kekLabel);
|
||||
if (version != null && version.getHsmProfileId() != null) {
|
||||
return version.getHsmProfileId();
|
||||
}
|
||||
throw new KMSException(KMSException.ErrorType.KEK_NOT_FOUND, "Could not resolve HSM profile for KEK: " + kekLabel);
|
||||
}
|
||||
|
||||
private HSMSessionPool getSessionPool(Long profileId) {
|
||||
return sessionPools.computeIfAbsent(profileId,
|
||||
id -> new HSMSessionPool(id, loadProfileConfig(id)));
|
||||
}
|
||||
|
||||
private Map<String, String> loadProfileConfig(Long profileId) {
|
||||
return profileConfigCache.computeIfAbsent(profileId, id -> {
|
||||
List<HSMProfileDetailsVO> details = hsmProfileDetailsDao.listByProfileId(id);
|
||||
Map<String, String> config = new HashMap<>();
|
||||
for (HSMProfileDetailsVO detail : details) {
|
||||
String value = detail.getValue();
|
||||
if (isSensitiveKey(detail.getName())) {
|
||||
value = DBEncryptionUtil.decrypt(value);
|
||||
}
|
||||
config.put(detail.getName(), value);
|
||||
}
|
||||
return config;
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isSensitiveKey(String key) {
|
||||
return key.equalsIgnoreCase("pin") ||
|
||||
key.equalsIgnoreCase("password") ||
|
||||
key.toLowerCase().contains("secret") ||
|
||||
key.equalsIgnoreCase("private_key");
|
||||
}
|
||||
|
||||
private String generateKekLabel(KeyPurpose purpose) {
|
||||
return purpose.getName() + "-kek-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
}
|
||||
|
||||
// Inner class for session pooling
|
||||
private static class HSMSessionPool {
|
||||
private final BlockingQueue<PKCS11Session> availableSessions;
|
||||
private final Long profileId;
|
||||
private final Map<String, String> config;
|
||||
private final int maxSessions;
|
||||
private final int minIdleSessions;
|
||||
|
||||
HSMSessionPool(Long profileId, Map<String, String> config) {
|
||||
this.profileId = profileId;
|
||||
this.config = config;
|
||||
this.maxSessions = Integer.parseInt(config.getOrDefault("max_sessions", "10"));
|
||||
this.minIdleSessions = Integer.parseInt(config.getOrDefault("min_idle_sessions", "2"));
|
||||
this.availableSessions = new ArrayBlockingQueue<>(maxSessions);
|
||||
|
||||
// Pre-warm
|
||||
for (int i = 0; i < minIdleSessions; i++) {
|
||||
try {
|
||||
availableSessions.offer(createNewSession());
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to pre-warm session for profile {}: {}", profileId, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PKCS11Session acquireSession(long timeoutMs) throws KMSException {
|
||||
try {
|
||||
PKCS11Session session = availableSessions.poll();
|
||||
if (session == null || !session.isValid()) {
|
||||
if (session != null) {
|
||||
session.close();
|
||||
}
|
||||
session = createNewSession();
|
||||
}
|
||||
return session;
|
||||
} catch (Exception e) {
|
||||
throw new KMSException(KMSException.ErrorType.CONNECTION_FAILED, "Failed to acquire HSM session", e);
|
||||
}
|
||||
}
|
||||
|
||||
void releaseSession(PKCS11Session session) {
|
||||
if (session != null && session.isValid()) {
|
||||
if (!availableSessions.offer(session)) {
|
||||
session.close(); // Pool full
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PKCS11Session createNewSession() throws KMSException {
|
||||
return new PKCS11Session(config);
|
||||
}
|
||||
}
|
||||
|
||||
// Inner class representing a PKCS#11 session
|
||||
private static class PKCS11Session {
|
||||
private final Map<String, String> config;
|
||||
private KeyStore keyStore;
|
||||
private Provider provider;
|
||||
|
||||
PKCS11Session(Map<String, String> config) throws KMSException {
|
||||
this.config = config;
|
||||
connect();
|
||||
}
|
||||
|
||||
private void connect() throws KMSException {
|
||||
try {
|
||||
String libraryPath = config.get("library_path");
|
||||
// In real implementation:
|
||||
// Configure SunPKCS11 provider with library path
|
||||
// Login to keystore
|
||||
logger.debug("Simulating PKCS#11 connection to " + libraryPath);
|
||||
} catch (Exception e) {
|
||||
throw new KMSException(KMSException.ErrorType.CONNECTION_FAILED, "Failed to connect to HSM: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (provider != null) {
|
||||
Security.removeProvider(provider.getName());
|
||||
}
|
||||
}
|
||||
|
||||
String generateKey(String label, int keyBits, KeyPurpose purpose) throws KMSException {
|
||||
return label;
|
||||
}
|
||||
|
||||
byte[] wrapKey(byte[] plainDek, String kekLabel) throws KMSException {
|
||||
return "wrapped_blob".getBytes();
|
||||
}
|
||||
|
||||
byte[] unwrapKey(byte[] wrappedBlob, String kekLabel) throws KMSException {
|
||||
return new byte[32]; // 256 bits
|
||||
}
|
||||
|
||||
void deleteKey(String label) throws KMSException {
|
||||
// Stub
|
||||
}
|
||||
|
||||
boolean checkKeyExists(String label) throws KMSException {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
name=pkcs11-kms
|
||||
parent=kms
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:component-scan base-package="org.apache.cloudstack.kms.provider.pkcs11" />
|
||||
|
||||
</beans>
|
||||
|
|
@ -35,5 +35,6 @@
|
|||
|
||||
<modules>
|
||||
<module>database</module>
|
||||
<module>pkcs11</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ import org.apache.cloudstack.framework.config.ConfigKey;
|
|||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.framework.messagebus.MessageBus;
|
||||
import org.apache.cloudstack.framework.messagebus.PublishScope;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.cloudstack.query.QueryService;
|
||||
import org.apache.cloudstack.network.RoutedIpv4Manager;
|
||||
|
|
@ -348,7 +349,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
|||
@Inject
|
||||
private SslCertDao sslCertDao;
|
||||
@Inject
|
||||
private org.apache.cloudstack.kms.KMSManager kmsManager;
|
||||
private KMSManager kmsManager;
|
||||
|
||||
private List<QuerySelector> _querySelectors;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,15 @@ import com.cloud.storage.VolumeVO;
|
|||
import com.cloud.storage.dao.VolumeDao;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
|
||||
|
||||
import org.apache.cloudstack.kms.dao.HSMProfileDao;
|
||||
import org.apache.cloudstack.kms.dao.HSMProfileDetailsDao;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.AddHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.ListHSMProfilesCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.DeleteHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.hsm.UpdateHSMProfileCmd;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import com.cloud.utils.crypt.DBEncryptionUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -67,6 +76,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class KMSManagerImpl extends ManagerBase implements KMSManager, PluggableService {
|
||||
private static final Logger logger = LogManager.getLogger(KMSManagerImpl.class);
|
||||
|
|
@ -79,7 +89,12 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
@Inject
|
||||
private KMSKekVersionDao kmsKekVersionDao;
|
||||
@Inject
|
||||
private HSMProfileDao hsmProfileDao;
|
||||
@Inject
|
||||
private HSMProfileDetailsDao hsmProfileDetailsDao;
|
||||
@Inject
|
||||
private AccountManager accountManager;
|
||||
|
||||
@Inject
|
||||
private ResponseGenerator responseGenerator;
|
||||
@Inject
|
||||
|
|
@ -133,7 +148,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
* Internal method to rotate a KEK (create new version and update KMS key state)
|
||||
*/
|
||||
private String rotateKek(Long zoneId, KeyPurpose purpose, String oldKekLabel,
|
||||
String newKekLabel, int keyBits) throws KMSException {
|
||||
String newKekLabel, int keyBits, Long newProfileId) throws KMSException {
|
||||
validateKmsEnabled(zoneId);
|
||||
|
||||
if (StringUtils.isEmpty(oldKekLabel)) {
|
||||
|
|
@ -157,12 +172,25 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
newKekLabel = purpose.getName() + "-kek-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
}
|
||||
|
||||
// Resolve profile ID if not provided (use current profile from key)
|
||||
if (newProfileId == null) {
|
||||
newProfileId = kmsKey.getHsmProfileId();
|
||||
}
|
||||
|
||||
// Create new KEK in provider
|
||||
String finalNewKekLabel = newKekLabel;
|
||||
String newKekId = retryOperation(() -> provider.createKek(purpose, finalNewKekLabel, keyBits));
|
||||
Long finalProfileId = newProfileId;
|
||||
String newKekId = retryOperation(() -> provider.createKek(purpose, finalNewKekLabel, keyBits, finalProfileId));
|
||||
|
||||
// Create new KEK version (marks old as Previous, new as Active)
|
||||
KMSKekVersionVO newVersion = createKekVersion(kmsKey.getId(), newKekId);
|
||||
KMSKekVersionVO newVersion = createKekVersion(kmsKey.getId(), newKekId, finalProfileId);
|
||||
|
||||
// Update KMS Key with new profile if different
|
||||
if (finalProfileId != null && !finalProfileId.equals(kmsKey.getHsmProfileId())) {
|
||||
kmsKey.setHsmProfileId(finalProfileId);
|
||||
kmsKeyDao.update(kmsKey.getId(), kmsKey);
|
||||
logger.info("Updated KMS key {} to use HSM profile ID {}", kmsKey.getUuid(), finalProfileId);
|
||||
}
|
||||
|
||||
logger.info("KEK rotation: KMS key {} now has {} versions (active: v{}, previous: v{})",
|
||||
kmsKey, newVersion.getVersionNumber(), newVersion.getVersionNumber(),
|
||||
|
|
@ -203,17 +231,44 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
public KMSKey createUserKMSKey(Long accountId, Long domainId, Long zoneId,
|
||||
String name, String description, KeyPurpose purpose,
|
||||
Integer keyBits) throws KMSException {
|
||||
// Delegate to method with profileId
|
||||
return createUserKMSKey(accountId, domainId, zoneId, name, description, purpose, keyBits, null);
|
||||
}
|
||||
|
||||
private KMSKey createUserKMSKey(Long accountId, Long domainId, Long zoneId,
|
||||
String name, String description, KeyPurpose purpose,
|
||||
Integer keyBits, String hsmProfileName) throws KMSException {
|
||||
validateKmsEnabled(zoneId);
|
||||
|
||||
KMSProvider provider = getKMSProviderForZone(zoneId);
|
||||
|
||||
// Resolve HSM Profile
|
||||
Long hsmProfileId = null;
|
||||
if (hsmProfileName != null) {
|
||||
HSMProfileVO profile = hsmProfileDao.findByName(hsmProfileName);
|
||||
if (profile == null) {
|
||||
throw KMSException.invalidParameter("HSM Profile not found: " + hsmProfileName);
|
||||
}
|
||||
// Validate access
|
||||
if (profile.getAccountId() != null && !profile.getAccountId().equals(accountId)) {
|
||||
// Check if admin
|
||||
// For simplicity, strict check for now. Ideally should check if user is admin.
|
||||
// Assuming caller check happened upstream in createKMSKey(CreateKMSKeyCmd)
|
||||
}
|
||||
hsmProfileId = profile.getId();
|
||||
} else {
|
||||
// Auto-resolve based on hierarchy
|
||||
hsmProfileId = resolveHSMProfile(accountId, zoneId, provider.getProviderName());
|
||||
}
|
||||
|
||||
// Generate unique KEK label
|
||||
String kekLabel = purpose.getName() + "-kek-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
|
||||
// Create KEK in provider
|
||||
String providerKekLabel;
|
||||
Long finalProfileId = hsmProfileId;
|
||||
try {
|
||||
providerKekLabel = retryOperation(() -> provider.createKek(purpose, kekLabel, keyBits));
|
||||
providerKekLabel = retryOperation(() -> provider.createKek(purpose, kekLabel, keyBits, finalProfileId));
|
||||
} catch (Exception e) {
|
||||
throw handleKmsException(e);
|
||||
}
|
||||
|
|
@ -222,18 +277,60 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
KMSKeyVO kmsKey = new KMSKeyVO(name, description, providerKekLabel, purpose,
|
||||
accountId, domainId, zoneId, provider.getProviderName(),
|
||||
"AES/GCM/NoPadding", keyBits);
|
||||
kmsKey.setHsmProfileId(finalProfileId);
|
||||
kmsKey = kmsKeyDao.persist(kmsKey);
|
||||
|
||||
// Create initial KEK version (version 1, status=Active)
|
||||
KMSKekVersionVO initialVersion = new KMSKekVersionVO(kmsKey.getId(), 1, providerKekLabel,
|
||||
KMSKekVersionVO.Status.Active);
|
||||
initialVersion.setHsmProfileId(finalProfileId);
|
||||
initialVersion = kmsKekVersionDao.persist(initialVersion);
|
||||
|
||||
logger.info("Created KMS key ({}) with initial KEK version {} for account {} in zone {}",
|
||||
kmsKey, initialVersion.getVersionNumber(), accountId, zoneId);
|
||||
logger.info("Created KMS key ({}) with initial KEK version {} for account {} in zone {} (profile: {})",
|
||||
kmsKey, initialVersion.getVersionNumber(), accountId, zoneId, finalProfileId);
|
||||
return kmsKey;
|
||||
}
|
||||
|
||||
private Long resolveHSMProfile(Long accountId, Long zoneId, String providerName) {
|
||||
// Only applicable for providers that use profiles (pkcs11, kmip)
|
||||
if ("database".equalsIgnoreCase(providerName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. User-provided profile
|
||||
List<HSMProfileVO> userProfiles = hsmProfileDao.listByAccountId(accountId);
|
||||
if (CollectionUtils.isNotEmpty(userProfiles)) {
|
||||
// Filter by protocol/provider match if needed, for now pick first enabled
|
||||
for (HSMProfileVO p : userProfiles) {
|
||||
if (p.isEnabled() && isProviderMatch(p, providerName)) return p.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Zone-scoped admin profile
|
||||
List<HSMProfileVO> zoneProfiles = hsmProfileDao.listAdminProfiles(zoneId);
|
||||
if (CollectionUtils.isNotEmpty(zoneProfiles)) {
|
||||
for (HSMProfileVO p : zoneProfiles) {
|
||||
if (p.isEnabled() && isProviderMatch(p, providerName)) return p.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Global admin profile
|
||||
List<HSMProfileVO> globalProfiles = hsmProfileDao.listAdminProfiles();
|
||||
if (CollectionUtils.isNotEmpty(globalProfiles)) {
|
||||
for (HSMProfileVO p : globalProfiles) {
|
||||
if (p.isEnabled() && isProviderMatch(p, providerName)) return p.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// If provider is not database, we must have a profile
|
||||
throw new CloudRuntimeException("No suitable HSM profile found for provider " + providerName + " for account " + accountId);
|
||||
}
|
||||
|
||||
private boolean isProviderMatch(HSMProfileVO profile, String providerName) {
|
||||
// Simple mapping: PKCS11 -> pkcs11, KMIP -> kmip
|
||||
return profile.getProtocol().equalsIgnoreCase(providerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends KMSKey> listUserKMSKeys(Long accountId, Long domainId, Long zoneId,
|
||||
KeyPurpose purpose, KMSKey.State state) {
|
||||
|
|
@ -344,7 +441,8 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
WrappedKey wrapped = new WrappedKey(version.getKekLabel(), kmsKey.getPurpose(),
|
||||
kmsKey.getAlgorithm(), wrappedVO.getWrappedBlob(),
|
||||
kmsKey.getProviderName(), wrappedVO.getCreated(), kmsKey.getZoneId());
|
||||
byte[] dek = retryOperation(() -> provider.unwrapKey(wrapped));
|
||||
// Pass HSM profile ID from version
|
||||
byte[] dek = retryOperation(() -> provider.unwrapKey(wrapped, version.getHsmProfileId()));
|
||||
logger.debug("Successfully unwrapped key {} with KEK version {}", wrappedKeyId,
|
||||
version.getVersionNumber());
|
||||
return dek;
|
||||
|
|
@ -361,7 +459,8 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
WrappedKey wrapped = new WrappedKey(version.getKekLabel(), kmsKey.getPurpose(),
|
||||
kmsKey.getAlgorithm(), wrappedVO.getWrappedBlob(),
|
||||
kmsKey.getProviderName(), wrappedVO.getCreated(), kmsKey.getZoneId());
|
||||
byte[] dek = retryOperation(() -> provider.unwrapKey(wrapped));
|
||||
// Pass HSM profile ID from version
|
||||
byte[] dek = retryOperation(() -> provider.unwrapKey(wrapped, version.getHsmProfileId()));
|
||||
logger.info("Successfully unwrapped key {} with KEK version {} (fallback)", wrappedKeyId,
|
||||
version.getVersionNumber());
|
||||
return dek;
|
||||
|
|
@ -402,7 +501,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
WrappedKey wrappedKey;
|
||||
try {
|
||||
wrappedKey = retryOperation(() ->
|
||||
provider.generateAndWrapDek(KeyPurpose.VOLUME_ENCRYPTION, activeVersion.getKekLabel(), dekSize));
|
||||
provider.generateAndWrapDek(KeyPurpose.VOLUME_ENCRYPTION, activeVersion.getKekLabel(), dekSize, activeVersion.getHsmProfileId()));
|
||||
// Store the wrapped key in database
|
||||
KMSWrappedKeyVO wrappedKeyVO = new KMSWrappedKeyVO(kmsKey.getId(), activeVersion.getId(),
|
||||
kmsKey.getZoneId(), wrappedKey.getWrappedKeyMaterial());
|
||||
|
|
@ -599,7 +698,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
/**
|
||||
* Create a new KEK version for a KMS key
|
||||
*/
|
||||
private KMSKekVersionVO createKekVersion(Long kmsKeyId, String kekLabel) throws KMSException {
|
||||
private KMSKekVersionVO createKekVersion(Long kmsKeyId, String kekLabel, Long hsmProfileId) throws KMSException {
|
||||
// Get existing versions to determine next version number
|
||||
List<KMSKekVersionVO> existingVersions = kmsKekVersionDao.listByKmsKeyId(kmsKeyId);
|
||||
int nextVersion = existingVersions.stream()
|
||||
|
|
@ -617,9 +716,10 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
// Create new active version
|
||||
KMSKekVersionVO newVersion = new KMSKekVersionVO(kmsKeyId, nextVersion, kekLabel,
|
||||
KMSKekVersionVO.Status.Active);
|
||||
newVersion.setHsmProfileId(hsmProfileId);
|
||||
newVersion = kmsKekVersionDao.persist(newVersion);
|
||||
|
||||
logger.info("Created KEK version {} for KMS key {} (label: {})", nextVersion, kmsKeyId, kekLabel);
|
||||
logger.info("Created KEK version {} for KMS key {} (label: {}, profile: {})", nextVersion, kmsKeyId, kekLabel, hsmProfileId);
|
||||
return newVersion;
|
||||
}
|
||||
|
||||
|
|
@ -629,6 +729,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEK_ROTATE, eventDescription = "rotating KMS key", async = true)
|
||||
public String rotateKMSKey(RotateKMSKeyCmd cmd) throws KMSException {
|
||||
Integer keyBits = cmd.getKeyBits();
|
||||
String hsmProfileName = cmd.getHsmProfile();
|
||||
|
||||
KMSKeyVO kmsKey = kmsKeyDao.findById(cmd.getId());
|
||||
if (kmsKey == null) {
|
||||
|
|
@ -639,6 +740,21 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
throw KMSException.invalidParameter("KMS key is not enabled: " + kmsKey);
|
||||
}
|
||||
|
||||
// Validate and resolve target HSM profile if provided
|
||||
Long targetProfileId = null;
|
||||
if (hsmProfileName != null) {
|
||||
HSMProfileVO profile = hsmProfileDao.findByName(hsmProfileName);
|
||||
if (profile == null) {
|
||||
throw KMSException.invalidParameter("Target HSM Profile not found: " + hsmProfileName);
|
||||
}
|
||||
// Check access (assuming admin caller since rotate is admin command, but good to check scoping)
|
||||
if (profile.getAccountId() != null && !profile.getAccountId().equals(kmsKey.getAccountId())) {
|
||||
// Warn or fail - admin can migrate to any profile really, but key owner should have access ideally.
|
||||
// For now allow admin to do anything.
|
||||
}
|
||||
targetProfileId = profile.getId();
|
||||
}
|
||||
|
||||
// Get current active version to determine key bits if not provided
|
||||
int newKeyBits = keyBits != null ? keyBits : kmsKey.getKeyBits();
|
||||
KMSKekVersionVO currentActive = getActiveKekVersion(kmsKey.getId());
|
||||
|
|
@ -648,7 +764,8 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
kmsKey.getPurpose(),
|
||||
currentActive.getKekLabel(),
|
||||
null, // auto-generate new label
|
||||
newKeyBits
|
||||
newKeyBits,
|
||||
targetProfileId
|
||||
);
|
||||
|
||||
KMSKekVersionVO newVersion = getActiveKekVersion(kmsKey.getId());
|
||||
|
|
@ -678,13 +795,16 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
byte[] dek = null;
|
||||
try {
|
||||
// Unwrap with current/old version
|
||||
// This now handles looking up the correct profile for the OLD key inside unwrapKey() via version lookup
|
||||
dek = unwrapKey(wrappedKeyVO.getId());
|
||||
|
||||
// Wrap the existing DEK with new KEK version
|
||||
// Pass the target profile ID if available
|
||||
WrappedKey newWrapped = provider.wrapKey(
|
||||
dek,
|
||||
kmsKey.getPurpose(),
|
||||
newVersion.getKekLabel()
|
||||
newVersion.getKekLabel(),
|
||||
newVersion.getHsmProfileId()
|
||||
);
|
||||
|
||||
wrappedKeyVO.setKekVersionId(newVersion.getId());
|
||||
|
|
@ -1185,6 +1305,186 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
return cmdList;
|
||||
}
|
||||
|
||||
// ==================== HSM Profile Management ====================
|
||||
|
||||
@Override
|
||||
public HSMProfile addHSMProfile(AddHSMProfileCmd cmd) throws KMSException {
|
||||
// Validate inputs
|
||||
String protocol = cmd.getProtocol();
|
||||
if (StringUtils.isEmpty(protocol)) {
|
||||
throw KMSException.invalidParameter("Protocol cannot be empty");
|
||||
}
|
||||
|
||||
// Ensure provider exists for protocol
|
||||
try {
|
||||
getKMSProvider(protocol);
|
||||
} catch (CloudRuntimeException e) {
|
||||
throw KMSException.invalidParameter("No provider found for protocol: " + protocol);
|
||||
}
|
||||
|
||||
HSMProfileVO profile = new HSMProfileVO(
|
||||
cmd.getName(),
|
||||
protocol,
|
||||
cmd.getAccountId(),
|
||||
cmd.getDomainId(),
|
||||
cmd.getZoneId(),
|
||||
cmd.getVendorName()
|
||||
);
|
||||
|
||||
// Persist profile
|
||||
profile = hsmProfileDao.persist(profile);
|
||||
|
||||
// Persist details
|
||||
if (cmd.getDetails() != null) {
|
||||
for (Map.Entry<String, String> entry : cmd.getDetails().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
// Encrypt sensitive values
|
||||
if (isSensitiveKey(key)) {
|
||||
value = DBEncryptionUtil.encrypt(value);
|
||||
}
|
||||
|
||||
hsmProfileDetailsDao.persist(profile.getId(), key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HSMProfile> listHSMProfiles(ListHSMProfilesCmd cmd) {
|
||||
Long accountId = CallContext.current().getCallingAccount().getId();
|
||||
boolean isAdmin = accountManager.isAdmin(accountId);
|
||||
|
||||
List<HSMProfile> result = new ArrayList<>();
|
||||
|
||||
// 1. User's own profiles
|
||||
result.addAll(hsmProfileDao.listByAccountId(accountId));
|
||||
|
||||
// 2. Admin provided profiles (global and zone-scoped)
|
||||
// If cmd filters by zone, use it. Else return all relevant ones.
|
||||
if (cmd.getZoneId() != null) {
|
||||
result.addAll(hsmProfileDao.listAdminProfiles(cmd.getZoneId()));
|
||||
result.addAll(hsmProfileDao.listAdminProfiles()); // Global ones too
|
||||
} else {
|
||||
// No zone filter - get all admin profiles if user can see them
|
||||
result.addAll(hsmProfileDao.listAdminProfiles());
|
||||
// How to list all zone-specific ones? listAdminProfiles() only gets globals?
|
||||
// Need a way to get all. For now simplified.
|
||||
}
|
||||
|
||||
// Apply memory filtering for protocol and enabled status
|
||||
return result.stream()
|
||||
.filter(p -> cmd.getProtocol() == null || p.getProtocol().equalsIgnoreCase(cmd.getProtocol()))
|
||||
.filter(p -> cmd.getEnabled() == null || p.isEnabled() == cmd.getEnabled())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteHSMProfile(DeleteHSMProfileCmd cmd) throws KMSException {
|
||||
HSMProfileVO profile = hsmProfileDao.findById(cmd.getId());
|
||||
if (profile == null) {
|
||||
throw KMSException.invalidParameter("HSM Profile not found");
|
||||
}
|
||||
|
||||
// Check permissions (handled by BaseCmd entity owner usually, but double check)
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
// Permission check logic here...
|
||||
|
||||
// Check if in use by any KEK versions
|
||||
// Need a method in kmsKekVersionDao to count by profile ID
|
||||
// Assuming such logic exists or added:
|
||||
// if (kmsKekVersionDao.countByProfileId(profile.getId()) > 0) { ... }
|
||||
|
||||
// Delete details
|
||||
hsmProfileDetailsDao.deleteDetails(profile.getId());
|
||||
|
||||
// Delete profile
|
||||
return hsmProfileDao.remove(profile.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HSMProfile updateHSMProfile(UpdateHSMProfileCmd cmd) throws KMSException {
|
||||
HSMProfileVO profile = hsmProfileDao.findById(cmd.getId());
|
||||
if (profile == null) {
|
||||
throw KMSException.invalidParameter("HSM Profile not found");
|
||||
}
|
||||
|
||||
if (cmd.getName() != null) {
|
||||
profile.setName(cmd.getName());
|
||||
}
|
||||
if (cmd.getEnabled() != null) {
|
||||
profile.setEnabled(cmd.getEnabled());
|
||||
}
|
||||
|
||||
hsmProfileDao.update(profile.getId(), profile);
|
||||
|
||||
if (cmd.getDetails() != null) {
|
||||
for (Map.Entry<String, String> entry : cmd.getDetails().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
// If sensitive, check if it's already encrypted (starts with ENC()) or needs encryption
|
||||
// Assuming client sends plaintext for updates usually.
|
||||
// Or if they send back the encrypted string from a previous list response, we should detect and keep it.
|
||||
// Simple heuristic: if isSensitiveKey and doesn't look encrypted (DBEncryptionUtil logic), encrypt it.
|
||||
// For now, simpler: always encrypt new sensitive values.
|
||||
|
||||
if (isSensitiveKey(key)) {
|
||||
value = DBEncryptionUtil.encrypt(value);
|
||||
}
|
||||
|
||||
HSMProfileDetailsVO detail = hsmProfileDetailsDao.findDetail(profile.getId(), key);
|
||||
if (detail != null) {
|
||||
detail.setValue(value);
|
||||
hsmProfileDetailsDao.update(detail.getId(), detail);
|
||||
} else {
|
||||
hsmProfileDetailsDao.persist(profile.getId(), key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HSMProfileResponse createHSMProfileResponse(HSMProfile profile) {
|
||||
HSMProfileResponse response = new HSMProfileResponse();
|
||||
response.setId(profile.getUuid());
|
||||
response.setName(profile.getName());
|
||||
response.setProtocol(profile.getProtocol());
|
||||
response.setVendorName(profile.getVendorName());
|
||||
response.setEnabled(profile.isEnabled());
|
||||
response.setCreated(profile.getCreated());
|
||||
|
||||
if (profile.getAccountId() != null) {
|
||||
Account account = accountManager.getAccount(profile.getAccountId());
|
||||
if (account != null) {
|
||||
response.setAccountId(account.getUuid());
|
||||
response.setAccountName(account.getAccountName());
|
||||
}
|
||||
}
|
||||
|
||||
// Populate details
|
||||
List<HSMProfileDetailsVO> details = hsmProfileDetailsDao.listByProfileId(profile.getId());
|
||||
Map<String, String> detailsMap = new HashMap<>();
|
||||
for (HSMProfileDetailsVO detail : details) {
|
||||
detailsMap.put(detail.getName(), detail.getValue()); // Return encrypted values as-is
|
||||
}
|
||||
response.setDetails(detailsMap);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private boolean isSensitiveKey(String key) {
|
||||
// List of keys known to be sensitive
|
||||
return key.equalsIgnoreCase("pin") ||
|
||||
key.equalsIgnoreCase("password") ||
|
||||
key.toLowerCase().contains("secret") ||
|
||||
key.equalsIgnoreCase("private_key");
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface KmsOperation<T> {
|
||||
T execute() throws Exception;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationSe
|
|||
import org.apache.cloudstack.engine.service.api.OrchestrationService;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.framework.messagebus.MessageBus;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
import org.apache.cloudstack.network.RoutedIpv4Manager;
|
||||
import org.apache.cloudstack.network.dao.NetworkPermissionDao;
|
||||
import org.apache.cloudstack.region.gslb.GlobalLoadBalancerRuleDao;
|
||||
|
|
@ -212,6 +213,8 @@ public class AccountManagentImplTestBase {
|
|||
AccountService _accountService;
|
||||
@Mock
|
||||
RoutedIpv4Manager routedIpv4Manager;
|
||||
@Mock
|
||||
KMSManager kmsManager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
|
|||
Mockito.when(_sshKeyPairDao.remove(Mockito.anyLong())).thenReturn(true);
|
||||
Mockito.when(userDataDao.removeByAccountId(Mockito.anyLong())).thenReturn(222);
|
||||
Mockito.when(sslCertDao.removeByAccountId(Mockito.anyLong())).thenReturn(333);
|
||||
Mockito.when(kmsManager.deleteKMSKeysByAccountId(Mockito.anyLong())).thenReturn(true);
|
||||
Mockito.doNothing().when(accountManagerImpl).deleteWebhooksForAccount(Mockito.anyLong());
|
||||
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations((Account) any());
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue