framework-jobs,server: fix password obfuscation for job result and password with display=false (#13388)

* framework-jobs: fix password obfuscation for job result

Fixes #13387

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* add password as hidden

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

---------

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-06-23 15:37:51 +05:30 committed by GitHub
parent b36f06482f
commit 2eb9820f3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 54 additions and 28 deletions

View File

@ -31,6 +31,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
@ -114,6 +116,8 @@ import com.cloud.vm.dao.VMInstanceDao;
import org.apache.logging.log4j.ThreadContext;
public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager, ClusterManagerListener, Configurable {
private static final Pattern PASSWORD_FIELD_PATTERN = Pattern.compile("\\\"password\\\":\\\"([^\\\"]*)\\\"+");
// Advanced
public static final ConfigKey<Long> JobExpireMinutes = new ConfigKey<Long>("Advanced", Long.class, "job.expire.minutes", "1440",
"Time (in minutes) for async-jobs to be kept in system", true, ConfigKey.Scope.Global);
@ -517,22 +521,26 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
}
public String obfuscatePassword(String result, boolean hidePassword) {
if (hidePassword) {
String pattern = "\"password\":";
if (result != null) {
if (result.contains(pattern)) {
String[] resp = result.split(pattern);
String psswd = resp[1].toString().split(",")[0];
if (psswd.endsWith("}")) {
psswd = psswd.substring(0, psswd.length() - 1);
result = resp[0] + pattern + psswd.replace(psswd.substring(2, psswd.length() - 1), "*****") + "}," + resp[1].split(",", 2)[1];
} else {
result = resp[0] + pattern + psswd.replace(psswd.substring(2, psswd.length() - 1), "*****") + "," + resp[1].split(",", 2)[1];
}
}
}
if (!hidePassword || StringUtils.isBlank(result)) {
return result;
}
return result;
Matcher matcher = PASSWORD_FIELD_PATTERN.matcher(result);
StringBuilder obfuscatedResult = new StringBuilder();
while (matcher.find()) {
String password = matcher.group(1);
String replacement = "\"password\":\"" + obfuscatePasswordValue(password) + "\"";
matcher.appendReplacement(obfuscatedResult, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(obfuscatedResult);
return obfuscatedResult.toString();
}
private String obfuscatePasswordValue(String password) {
if (StringUtils.isEmpty(password)) {
return password;
}
return password.charAt(0) + "*****";
}
private void scheduleExecution(final AsyncJobVO job) {

View File

@ -17,12 +17,15 @@
package org.apache.cloudstack.framework.jobs;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobManagerImpl;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.utils.HumanReadableJson;
@RunWith (MockitoJUnitRunner.class)
public class AsyncJobManagerTest {
@ -37,6 +40,12 @@ public class AsyncJobManagerTest {
String inputNoBraces = "\"password\":\"password\"\",\"action\":\"OFF\"";
String expectedNoBraces = "\"password\":\"p*****\",\"action\":\"OFF\"";
String realUserVmResponseWithPasswordInput = "{\"id\":\"f75b0990-5801-4b78-bcb0-58a503afa49c\",\"name\":\"pw-vm\"," +
"\"displayname\":\"pw-vm\",\"account\":\"admin\",\"password\":\"67wSK5\",\"instancename\":\"i-2-17-VM\"," +
"\"details\":{\"password\":\"3WTVryPJZJwMZGcJJ+OOYf84+uixk/1FraomPG9N6/Uvng\\u003d\\u003d\"," +
"\"Message.ReservedCapacityFreed.Flag\":\"true\",\"rootDiskController\":\"osdefault\"}," +
"\"arch\":\"x86_64\",\"jobid\":\"c13865d3-61ec-4269-979a-3d799181d5fe\",\"jobstatus\":0}";
@Test
public void obfuscatePasswordTest() {
String result = asyncJobManager.obfuscatePassword(input, true);
@ -79,4 +88,15 @@ public class AsyncJobManagerTest {
Assert.assertEquals(noPassword, result);
}
@Test
public void obfuscatePasswordTestHidePasswordRealInput() {
String result = asyncJobManager.obfuscatePassword(realUserVmResponseWithPasswordInput, true);
Assert.assertNotNull(result);
Assert.assertFalse(result.contains("\"password\":\"3WTVryPJZJwMZGcJJ+OOYf84+uixk\""));
String jsonObject = HumanReadableJson.getHumanReadableBytesJson(result);
Assert.assertTrue(StringUtils.isNotEmpty(jsonObject));
Assert.assertTrue(jsonObject.contains("\"password\":\"3*****\""));
}
}

View File

@ -24,28 +24,21 @@ import java.util.Set;
import javax.inject.Inject;
import org.apache.cloudstack.network.BgpPeer;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.vm.VirtualMachineProfileImpl;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.NicDao;
import com.google.gson.Gson;
import org.apache.cloudstack.api.command.admin.router.ConfigureOvsElementCmd;
import org.apache.cloudstack.api.command.admin.router.ConfigureVirtualRouterElementCmd;
import org.apache.cloudstack.api.command.admin.router.CreateVirtualRouterElementCmd;
import org.apache.cloudstack.api.command.admin.router.ListOvsElementsCmd;
import org.apache.cloudstack.api.command.admin.router.ListVirtualRouterElementsCmd;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.network.BgpPeer;
import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinition;
import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinitionBuilder;
import org.apache.cloudstack.network.topology.NetworkTopology;
import org.apache.cloudstack.network.topology.NetworkTopologyContext;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.cloud.agent.api.to.LoadBalancerTO;
import com.cloud.configuration.ConfigurationManager;
@ -101,6 +94,7 @@ import com.cloud.network.vpc.Vpc;
import com.cloud.offering.NetworkOffering;
import com.cloud.offerings.NetworkOfferingVO;
import com.cloud.offerings.dao.NetworkOfferingDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.component.AdapterBase;
@ -117,8 +111,12 @@ import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.VirtualMachineProfileImpl;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.DomainRouterDao;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
import com.google.gson.Gson;
public class VirtualRouterElement extends AdapterBase implements VirtualRouterElementService, DhcpServiceProvider, UserDataServiceProvider, SourceNatServiceProvider,
StaticNatServiceProvider, FirewallServiceProvider, LoadBalancingServiceProvider, PortForwardingServiceProvider, RemoteAccessVPNServiceProvider, IpDeployer,
@ -736,7 +734,7 @@ NetworkMigrationResponder, AggregatedCommandExecutor, RedundantResource, DnsServ
_userVmDao.loadDetails(userVmVO);
userVmVO.setDetail(VmDetailConstants.PASSWORD, password_encrypted);
_userVmDao.saveDetails(userVmVO);
_userVmDao.saveDetails(userVmVO, List.of(VmDetailConstants.PASSWORD));
userVmVO.setUpdateParameters(true);
_userVmDao.update(userVmVO.getId(), userVmVO);