diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java index 562d908507e8..b2e5ef55ee64 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java @@ -42,6 +42,7 @@ import org.apache.commons.lang3.StringUtils; import com.cloud.utils.Pair; +import com.cloud.utils.db.Encrypt; import com.cloud.utils.db.GenericDao; import com.cloud.utils.net.NetUtils; @@ -85,6 +86,7 @@ public class AutoScaleVmProfileVO implements AutoScaleVmProfile, Identity, Inter @Column(name = "counter_params", updatable = true) private String counterParams; + @Encrypt @Column(name = "user_data", updatable = true, nullable = true, length = 1048576) @Basic(fetch = FetchType.LAZY) private String userData; diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42300to2400.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42300to2400.java index ce217cef9e75..e4f143236933 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42300to2400.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42300to2400.java @@ -16,6 +16,14 @@ // under the License. package com.cloud.upgrade.dao; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.cloud.utils.crypt.DBEncryptionUtil; +import com.cloud.utils.exception.CloudRuntimeException; + public class Upgrade42300to2400 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { @Override @@ -27,4 +35,26 @@ public String[] getUpgradableVersionRange() { public String getUpgradedVersion() { return "24.0.0"; } + + @Override + public void performDataMigration(Connection conn) { + encryptUserData(conn, "user_vm"); + encryptUserData(conn, "user_data"); + encryptUserData(conn, "autoscale_vmprofiles"); + } + + private void encryptUserData(Connection conn, String table) { + try (PreparedStatement selectStatement = conn.prepareStatement(String.format("SELECT `id`, `user_data` FROM `cloud`.`%s` WHERE `user_data` IS NOT NULL", table)); + ResultSet resultSet = selectStatement.executeQuery(); + PreparedStatement updateStatement = conn.prepareStatement(String.format("UPDATE `cloud`.`%s` SET `user_data` = ? WHERE `id` = ?", table))) { + while (resultSet.next()) { + updateStatement.setString(1, DBEncryptionUtil.encrypt(resultSet.getString(2))); + updateStatement.setLong(2, resultSet.getLong(1)); + updateStatement.executeUpdate(); + } + } catch (SQLException e) { + throw new CloudRuntimeException(String.format("Failed to encrypt user_data in table %s", table), e); + } + logger.debug("Encrypted user_data in table {}", table); + } } diff --git a/engine/schema/src/main/java/com/cloud/user/UserDataVO.java b/engine/schema/src/main/java/com/cloud/user/UserDataVO.java index e8864976069d..3359c2c1311e 100644 --- a/engine/schema/src/main/java/com/cloud/user/UserDataVO.java +++ b/engine/schema/src/main/java/com/cloud/user/UserDataVO.java @@ -28,6 +28,7 @@ import java.util.Date; import java.util.UUID; +import com.cloud.utils.db.Encrypt; import com.cloud.utils.db.GenericDao; @Entity @@ -55,6 +56,7 @@ public UserDataVO() { @Column(name = "name") private String name; + @Encrypt @Column(name = "user_data", updatable = true, length = 1048576) @Basic(fetch = FetchType.LAZY) private String userData; diff --git a/engine/schema/src/main/java/com/cloud/user/dao/UserDataDaoImpl.java b/engine/schema/src/main/java/com/cloud/user/dao/UserDataDaoImpl.java index ded0cd172da3..fc1527fe91b7 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/UserDataDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/UserDataDaoImpl.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.user.dao; +import java.util.Objects; + import com.cloud.user.UserDataVO; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; @@ -34,7 +36,6 @@ public UserDataDaoImpl() { userdataSearch = createSearchBuilder(); userdataSearch.and("accountId", userdataSearch.entity().getAccountId(), SearchCriteria.Op.EQ); userdataSearch.and("domainId", userdataSearch.entity().getDomainId(), SearchCriteria.Op.EQ); - userdataSearch.and("userData", userdataSearch.entity().getUserData(), SearchCriteria.Op.EQ); userdataSearch.done(); userdataByNameSearch = createSearchBuilder(); @@ -49,9 +50,9 @@ public UserDataVO findByUserData(long accountId, long domainId, String userData) SearchCriteria sc = userdataSearch.create(); sc.setParameters("accountId", accountId); sc.setParameters("domainId", domainId); - sc.setParameters("userData", userData); - return findOneBy(sc); + // user_data is encrypted non-deterministically, so compare the decrypted values + return listBy(sc).stream().filter(vo -> Objects.equals(vo.getUserData(), userData)).findFirst().orElse(null); } @Override diff --git a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java index ce3a9a84a34f..e6111c9d952c 100644 --- a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java @@ -28,6 +28,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.uservm.UserVm; +import com.cloud.utils.db.Encrypt; import org.apache.commons.lang3.StringUtils; @Entity @@ -39,6 +40,7 @@ public class UserVmVO extends VMInstanceVO implements UserVm { @Column(name = "iso_id", nullable = true, length = 17) private Long isoId = null; + @Encrypt @Column(name = "user_data", updatable = true, nullable = true, length = 1048576) @Basic(fetch = FetchType.LAZY) private String userData; diff --git a/engine/schema/src/test/java/com/cloud/user/dao/UserDataDaoImplTest.java b/engine/schema/src/test/java/com/cloud/user/dao/UserDataDaoImplTest.java new file mode 100644 index 000000000000..9051250c5858 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/user/dao/UserDataDaoImplTest.java @@ -0,0 +1,42 @@ +// 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 com.cloud.user.dao; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import com.cloud.user.UserDataVO; +import com.cloud.utils.db.SearchCriteria; + +public class UserDataDaoImplTest { + + @Test + public void findByUserDataMatchesOnDecryptedValue() { + UserDataDaoImpl userDataDao = Mockito.spy(new UserDataDaoImpl()); + UserDataVO other = Mockito.mock(UserDataVO.class); + Mockito.when(other.getUserData()).thenReturn("b3RoZXI="); + UserDataVO match = Mockito.mock(UserDataVO.class); + Mockito.when(match.getUserData()).thenReturn("I2Nsb3VkLWNvbmZpZw=="); + Mockito.doReturn(List.of(other, match)).when(userDataDao).listBy(Mockito.any(SearchCriteria.class)); + + Assert.assertEquals(match, userDataDao.findByUserData(1L, 1L, "I2Nsb3VkLWNvbmZpZw==")); + Assert.assertNull(userDataDao.findByUserData(1L, 1L, "bm9uZQ==")); + } +} diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java index 6f84bd9b1e3e..c099bc812eda 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java @@ -45,6 +45,7 @@ import com.cloud.storage.Volume; import com.cloud.user.Account; import com.cloud.util.StoragePoolTypeConverter; +import com.cloud.utils.db.Encrypt; import com.cloud.utils.db.GenericDao; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; @@ -411,6 +412,7 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro @Column(name = "nic_dns_name") private String nicDnsName; + @Encrypt @Column(name = "user_data", updatable = true, nullable = true, length = 2048) private String userData;