From 46c50af68b4773d1d48747fcab83b3ef5fa0a8ee Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 23 Sep 2025 09:42:54 -0400 Subject: [PATCH 1/2] Rdpfactory (#7) * Implement RDP proxy module with Sentrius integration --- .local.env | 6 +- .local.env.bak | 6 +- .../controllers/api/HostApiController.java | 2 +- .../startup/ConfigurationApplicationTask.java | 120 +++++++++++------- .../templates/sso/enclaves/list_servers.html | 4 +- .../sentrius/sso/core/dto/HostSystemDTO.java | 7 +- .../sso/core/services/security/JwtUtil.java | 16 +++ .../sentrius/sso/core/model/HostSystem.java | 17 ++- .../core/repository/ProfileRepository.java | 5 + .../sso/core/services/UserService.java | 10 ++ .../IntegrationSecurityTokenService.java | 1 - .../realms/sentrius-realm.json.template | 5 +- docker/sentrius/demoInstaller.yml | 7 +- ops-scripts/base/build-images.sh | 22 +++- ops-scripts/local/deploy-helm.sh | 2 +- .../sso/rdpproxy/config/SecurityConfig.java | 1 + .../security/RdpProxySecurityConfig.java | 1 + 17 files changed, 157 insertions(+), 75 deletions(-) diff --git a/.local.env b/.local.env index cfa37f72..4eecd0ea 100644 --- a/.local.env +++ b/.local.env @@ -1,10 +1,10 @@ -SENTRIUS_VERSION=1.1.510 +SENTRIUS_VERSION=1.1.523 SENTRIUS_SSH_VERSION=1.1.45 -SENTRIUS_KEYCLOAK_VERSION=1.1.60 +SENTRIUS_KEYCLOAK_VERSION=1.1.64 SENTRIUS_AGENT_VERSION=1.1.51 SENTRIUS_AI_AGENT_VERSION=1.1.287 LLMPROXY_VERSION=1.0.88 LAUNCHER_VERSION=1.0.91 AGENTPROXY_VERSION=1.0.92 SSHPROXY_VERSION=1.0.91 -RDPPROXY_VERSION=1.0.122 \ No newline at end of file +RDPPROXY_VERSION=1.0.122 diff --git a/.local.env.bak b/.local.env.bak index cfa37f72..4eecd0ea 100644 --- a/.local.env.bak +++ b/.local.env.bak @@ -1,10 +1,10 @@ -SENTRIUS_VERSION=1.1.510 +SENTRIUS_VERSION=1.1.523 SENTRIUS_SSH_VERSION=1.1.45 -SENTRIUS_KEYCLOAK_VERSION=1.1.60 +SENTRIUS_KEYCLOAK_VERSION=1.1.64 SENTRIUS_AGENT_VERSION=1.1.51 SENTRIUS_AI_AGENT_VERSION=1.1.287 LLMPROXY_VERSION=1.0.88 LAUNCHER_VERSION=1.0.91 AGENTPROXY_VERSION=1.0.92 SSHPROXY_VERSION=1.0.91 -RDPPROXY_VERSION=1.0.122 \ No newline at end of file +RDPPROXY_VERSION=1.0.122 diff --git a/api/src/main/java/io/sentrius/sso/controllers/api/HostApiController.java b/api/src/main/java/io/sentrius/sso/controllers/api/HostApiController.java index c724135a..6dc89621 100644 --- a/api/src/main/java/io/sentrius/sso/controllers/api/HostApiController.java +++ b/api/src/main/java/io/sentrius/sso/controllers/api/HostApiController.java @@ -317,7 +317,7 @@ public ResponseEntity> initiateRdpSession( // Validate access to the host group Optional hostGroupOpt = hostGroupService.getHostGroupWithHostSystems(user, enclaveId); - if (hostGroupOpt.isEmpty()) { + if (!AccessUtil.canAccess(user, SSHAccessEnum.CAN_MANAGE_SYSTEMS) && hostGroupOpt.isEmpty() ) { // log.warn("User {} does not have access to host group {}", user.getUsername(), enclaveId); return ResponseEntity.badRequest().build(); } diff --git a/api/src/main/java/io/sentrius/sso/startup/ConfigurationApplicationTask.java b/api/src/main/java/io/sentrius/sso/startup/ConfigurationApplicationTask.java index 729b18ca..404959e8 100644 --- a/api/src/main/java/io/sentrius/sso/startup/ConfigurationApplicationTask.java +++ b/api/src/main/java/io/sentrius/sso/startup/ConfigurationApplicationTask.java @@ -2,7 +2,9 @@ import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.security.DigestInputStream; import java.security.GeneralSecurityException; @@ -14,6 +16,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -89,60 +92,82 @@ public class ConfigurationApplicationTask { @EventListener(ApplicationReadyEvent.class) @Transactional public void afterStartup() throws IOException, GeneralSecurityException, JSchException, SQLException { - // Your logic here + String yamlPath = systemOptions.getYamlConfiguration(); + if (StringUtils.isEmpty(yamlPath)) { + log.info("No configuration file found"); + return; + } - if (!StringUtils.isEmpty(systemOptions.getYamlConfiguration())) { - log.info("Checking for configuration file {}", systemOptions.getYamlConfiguration()); - var digestStream = new DigestInputStream( - new FileInputStream(systemOptions.getYamlConfiguration()), - MessageDigest.getInstance("SHA256") - ); - MessageDigest digest = digestStream.getMessageDigest(); - var hash = new String(digest.digest()); - - AtomicBoolean recreate = new AtomicBoolean(false); - configurationOptionRepository.findLatestByConfigurationName("yamlConfigurationFileHash") - .ifPresentOrElse( - configurationOption -> { - if (!hash.equals(configurationOption.getConfigurationValue())) { - log.info("Configuration file hash has changed, recreating database"); - recreate.set(true); - }else { - log.info("Configuration file hash has not changed"); - } - configurationOption.setConfigurationValue(hash); - configurationOptionRepository.save(configurationOption); - }, - () -> { - log.info("No configuration file hash found, creating one"); - var configurationOption = new ConfigurationOption(); - configurationOption.setConfigurationName("yamlConfigurationFileHash"); - configurationOption.setConfigurationValue(hash); - configurationOptionRepository.save(configurationOption); - recreate.set(true); - } - ); + Path yamlFile = Paths.get(yamlPath); + if (!Files.exists(yamlFile)) { + log.warn("Configuration file {} not found", yamlFile); + return; + } - Boolean deleteFile = systemOptions.getDeleteYamlConfigurationFile(); - if (null == deleteFile) { - deleteFile = true; - } - if (deleteFile) { - Files.delete(Paths.get(systemOptions.getYamlConfiguration())); + log.info("Checking for configuration file {}", yamlFile); + + // --- Compute SHA-256 hash of file --- + String hash; + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + try (InputStream in = Files.newInputStream(yamlFile); + DigestInputStream dis = new DigestInputStream(in, md)) { + byte[] buffer = new byte[8192]; + while (dis.read(buffer) != -1) { + // Consume stream fully to update digest + } } + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(digest.length * 2); + for (byte b : digest) sb.append(String.format("%02x", b)); + hash = sb.toString(); + } catch (Exception e) { + throw new IOException("Unable to compute SHA-256 for " + yamlFile, e); + } + + AtomicBoolean recreate = new AtomicBoolean(false); + + configurationOptionRepository.findLatestByConfigurationName("yamlConfigurationFileHash") + .ifPresentOrElse(existing -> { + String oldHash = existing.getConfigurationValue(); + if (!hash.equalsIgnoreCase(oldHash)) { + log.info("Configuration file hash changed. Recreating database."); + recreate.set(true); + } else { + log.info("Configuration file hash unchanged ({}).", hash); + } + existing.setConfigurationValue(hash); + configurationOptionRepository.save(existing); + }, () -> { + log.info("No configuration file hash found; creating one."); + var option = new ConfigurationOption(); + option.setConfigurationName("yamlConfigurationFileHash"); + option.setConfigurationValue(hash); + configurationOptionRepository.save(option); + recreate.set(true); + }); + + Boolean deleteFile = systemOptions.getDeleteYamlConfigurationFile(); + if (deleteFile == null) deleteFile = true; - if (recreate.get()) { - log.info("Recreating database"); - var installConfiguration = - InstallConfiguration.fromYaml(new FileInputStream(systemOptions.getYamlConfiguration())); - // recreate the database + if (deleteFile) { + try { + Files.deleteIfExists(yamlFile); + log.info("Deleted configuration file {}", yamlFile); + } catch (IOException e) { + log.warn("Failed to delete configuration file {}", yamlFile, e); + } + } + if (recreate.get()) { + log.info("Recreating database using configuration {}", yamlFile); + try (InputStream in = Files.newInputStream(yamlFile)) { + var installConfiguration = InstallConfiguration.fromYaml(in); initialize(installConfiguration, true); } - } else { - log.info("No configuration file found"); } } + @Transactional public List createStaticType(UserType type, boolean action) throws SQLException, GeneralSecurityException { @@ -498,9 +523,11 @@ protected List createSystems(InstallConfiguration installConfigurati if (null != installConfiguration.getSystems()) { for (var system : installConfiguration.getSystems()) { var systemObj = HostSystem.fromDTO(system); + log.info("Processing system {} with host {} and port {}", systemObj.getDisplayName(), systemObj.getHost(), systemObj.getPort()); if ( shouldInsertSystem(systemObj)) { if (action) { var sys = systemRepository.save(systemObj); + log.info("Creating system {}, with id {}", system.getDisplayName(), sys.getId()); } sideEffects.add( SideEffect.builder().sideEffectDescription("Creating system " + system.getDisplayName()).type( @@ -518,7 +545,8 @@ protected boolean shouldInsertSystem(HostSystem systemObj) { return true; } for(HostSystem system : systems) { - if (system.getHost().equals(systemObj.getHost()) && system.getPort() == systemObj.getPort()) { + if (system.getHost().equals(systemObj.getHost()) && Objects.equals(system.getPort(), systemObj.getPort())) { + log.info("System {} with host {} and port {} already exists", systemObj.getDisplayName(), systemObj.getHost(), systemObj.getPort()); return false; } } diff --git a/api/src/main/resources/templates/sso/enclaves/list_servers.html b/api/src/main/resources/templates/sso/enclaves/list_servers.html index 14f09adc..2606f213 100755 --- a/api/src/main/resources/templates/sso/enclaves/list_servers.html +++ b/api/src/main/resources/templates/sso/enclaves/list_servers.html @@ -167,7 +167,6 @@

Assigned RDP Servers

System Name Hostname Host Enclave - User Operations @@ -626,8 +625,7 @@