Skip to content

Commit 46c50af

Browse files
committed
Rdpfactory (#7)
* Implement RDP proxy module with Sentrius integration
1 parent 5ec147b commit 46c50af

File tree

17 files changed

+157
-75
lines changed

17 files changed

+157
-75
lines changed

.local.env

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
SENTRIUS_VERSION=1.1.510
1+
SENTRIUS_VERSION=1.1.523
22
SENTRIUS_SSH_VERSION=1.1.45
3-
SENTRIUS_KEYCLOAK_VERSION=1.1.60
3+
SENTRIUS_KEYCLOAK_VERSION=1.1.64
44
SENTRIUS_AGENT_VERSION=1.1.51
55
SENTRIUS_AI_AGENT_VERSION=1.1.287
66
LLMPROXY_VERSION=1.0.88
77
LAUNCHER_VERSION=1.0.91
88
AGENTPROXY_VERSION=1.0.92
99
SSHPROXY_VERSION=1.0.91
10-
RDPPROXY_VERSION=1.0.122
10+
RDPPROXY_VERSION=1.0.122

.local.env.bak

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
SENTRIUS_VERSION=1.1.510
1+
SENTRIUS_VERSION=1.1.523
22
SENTRIUS_SSH_VERSION=1.1.45
3-
SENTRIUS_KEYCLOAK_VERSION=1.1.60
3+
SENTRIUS_KEYCLOAK_VERSION=1.1.64
44
SENTRIUS_AGENT_VERSION=1.1.51
55
SENTRIUS_AI_AGENT_VERSION=1.1.287
66
LLMPROXY_VERSION=1.0.88
77
LAUNCHER_VERSION=1.0.91
88
AGENTPROXY_VERSION=1.0.92
99
SSHPROXY_VERSION=1.0.91
10-
RDPPROXY_VERSION=1.0.122
10+
RDPPROXY_VERSION=1.0.122

api/src/main/java/io/sentrius/sso/controllers/api/HostApiController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ public ResponseEntity<Map<String, Object>> initiateRdpSession(
317317

318318
// Validate access to the host group
319319
Optional<HostGroup> hostGroupOpt = hostGroupService.getHostGroupWithHostSystems(user, enclaveId);
320-
if (hostGroupOpt.isEmpty()) {
320+
if (!AccessUtil.canAccess(user, SSHAccessEnum.CAN_MANAGE_SYSTEMS) && hostGroupOpt.isEmpty() ) {
321321
// log.warn("User {} does not have access to host group {}", user.getUsername(), enclaveId);
322322
return ResponseEntity.badRequest().build();
323323
}

api/src/main/java/io/sentrius/sso/startup/ConfigurationApplicationTask.java

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import java.io.FileInputStream;
44
import java.io.IOException;
5+
import java.io.InputStream;
56
import java.nio.file.Files;
7+
import java.nio.file.Path;
68
import java.nio.file.Paths;
79
import java.security.DigestInputStream;
810
import java.security.GeneralSecurityException;
@@ -14,6 +16,7 @@
1416
import java.util.HashSet;
1517
import java.util.List;
1618
import java.util.Map;
19+
import java.util.Objects;
1720
import java.util.Optional;
1821
import java.util.Set;
1922
import java.util.UUID;
@@ -89,60 +92,82 @@ public class ConfigurationApplicationTask {
8992
@EventListener(ApplicationReadyEvent.class)
9093
@Transactional
9194
public void afterStartup() throws IOException, GeneralSecurityException, JSchException, SQLException {
92-
// Your logic here
95+
String yamlPath = systemOptions.getYamlConfiguration();
96+
if (StringUtils.isEmpty(yamlPath)) {
97+
log.info("No configuration file found");
98+
return;
99+
}
93100

94-
if (!StringUtils.isEmpty(systemOptions.getYamlConfiguration())) {
95-
log.info("Checking for configuration file {}", systemOptions.getYamlConfiguration());
96-
var digestStream = new DigestInputStream(
97-
new FileInputStream(systemOptions.getYamlConfiguration()),
98-
MessageDigest.getInstance("SHA256")
99-
);
100-
MessageDigest digest = digestStream.getMessageDigest();
101-
var hash = new String(digest.digest());
102-
103-
AtomicBoolean recreate = new AtomicBoolean(false);
104-
configurationOptionRepository.findLatestByConfigurationName("yamlConfigurationFileHash")
105-
.ifPresentOrElse(
106-
configurationOption -> {
107-
if (!hash.equals(configurationOption.getConfigurationValue())) {
108-
log.info("Configuration file hash has changed, recreating database");
109-
recreate.set(true);
110-
}else {
111-
log.info("Configuration file hash has not changed");
112-
}
113-
configurationOption.setConfigurationValue(hash);
114-
configurationOptionRepository.save(configurationOption);
115-
},
116-
() -> {
117-
log.info("No configuration file hash found, creating one");
118-
var configurationOption = new ConfigurationOption();
119-
configurationOption.setConfigurationName("yamlConfigurationFileHash");
120-
configurationOption.setConfigurationValue(hash);
121-
configurationOptionRepository.save(configurationOption);
122-
recreate.set(true);
123-
}
124-
);
101+
Path yamlFile = Paths.get(yamlPath);
102+
if (!Files.exists(yamlFile)) {
103+
log.warn("Configuration file {} not found", yamlFile);
104+
return;
105+
}
125106

126-
Boolean deleteFile = systemOptions.getDeleteYamlConfigurationFile();
127-
if (null == deleteFile) {
128-
deleteFile = true;
129-
}
130-
if (deleteFile) {
131-
Files.delete(Paths.get(systemOptions.getYamlConfiguration()));
107+
log.info("Checking for configuration file {}", yamlFile);
108+
109+
// --- Compute SHA-256 hash of file ---
110+
String hash;
111+
try {
112+
MessageDigest md = MessageDigest.getInstance("SHA-256");
113+
try (InputStream in = Files.newInputStream(yamlFile);
114+
DigestInputStream dis = new DigestInputStream(in, md)) {
115+
byte[] buffer = new byte[8192];
116+
while (dis.read(buffer) != -1) {
117+
// Consume stream fully to update digest
118+
}
132119
}
120+
byte[] digest = md.digest();
121+
StringBuilder sb = new StringBuilder(digest.length * 2);
122+
for (byte b : digest) sb.append(String.format("%02x", b));
123+
hash = sb.toString();
124+
} catch (Exception e) {
125+
throw new IOException("Unable to compute SHA-256 for " + yamlFile, e);
126+
}
127+
128+
AtomicBoolean recreate = new AtomicBoolean(false);
129+
130+
configurationOptionRepository.findLatestByConfigurationName("yamlConfigurationFileHash")
131+
.ifPresentOrElse(existing -> {
132+
String oldHash = existing.getConfigurationValue();
133+
if (!hash.equalsIgnoreCase(oldHash)) {
134+
log.info("Configuration file hash changed. Recreating database.");
135+
recreate.set(true);
136+
} else {
137+
log.info("Configuration file hash unchanged ({}).", hash);
138+
}
139+
existing.setConfigurationValue(hash);
140+
configurationOptionRepository.save(existing);
141+
}, () -> {
142+
log.info("No configuration file hash found; creating one.");
143+
var option = new ConfigurationOption();
144+
option.setConfigurationName("yamlConfigurationFileHash");
145+
option.setConfigurationValue(hash);
146+
configurationOptionRepository.save(option);
147+
recreate.set(true);
148+
});
149+
150+
Boolean deleteFile = systemOptions.getDeleteYamlConfigurationFile();
151+
if (deleteFile == null) deleteFile = true;
133152

134-
if (recreate.get()) {
135-
log.info("Recreating database");
136-
var installConfiguration =
137-
InstallConfiguration.fromYaml(new FileInputStream(systemOptions.getYamlConfiguration()));
138-
// recreate the database
153+
if (deleteFile) {
154+
try {
155+
Files.deleteIfExists(yamlFile);
156+
log.info("Deleted configuration file {}", yamlFile);
157+
} catch (IOException e) {
158+
log.warn("Failed to delete configuration file {}", yamlFile, e);
159+
}
160+
}
139161

162+
if (recreate.get()) {
163+
log.info("Recreating database using configuration {}", yamlFile);
164+
try (InputStream in = Files.newInputStream(yamlFile)) {
165+
var installConfiguration = InstallConfiguration.fromYaml(in);
140166
initialize(installConfiguration, true);
141167
}
142-
} else {
143-
log.info("No configuration file found");
144168
}
145169
}
170+
146171
@Transactional
147172
public List<SideEffect> createStaticType(UserType type, boolean action) throws SQLException,
148173
GeneralSecurityException {
@@ -498,9 +523,11 @@ protected List<SideEffect> createSystems(InstallConfiguration installConfigurati
498523
if (null != installConfiguration.getSystems()) {
499524
for (var system : installConfiguration.getSystems()) {
500525
var systemObj = HostSystem.fromDTO(system);
526+
log.info("Processing system {} with host {} and port {}", systemObj.getDisplayName(), systemObj.getHost(), systemObj.getPort());
501527
if ( shouldInsertSystem(systemObj)) {
502528
if (action) {
503529
var sys = systemRepository.save(systemObj);
530+
log.info("Creating system {}, with id {}", system.getDisplayName(), sys.getId());
504531
}
505532
sideEffects.add(
506533
SideEffect.builder().sideEffectDescription("Creating system " + system.getDisplayName()).type(
@@ -518,7 +545,8 @@ protected boolean shouldInsertSystem(HostSystem systemObj) {
518545
return true;
519546
}
520547
for(HostSystem system : systems) {
521-
if (system.getHost().equals(systemObj.getHost()) && system.getPort() == systemObj.getPort()) {
548+
if (system.getHost().equals(systemObj.getHost()) && Objects.equals(system.getPort(), systemObj.getPort())) {
549+
log.info("System {} with host {} and port {} already exists", systemObj.getDisplayName(), systemObj.getHost(), systemObj.getPort());
522550
return false;
523551
}
524552
}

api/src/main/resources/templates/sso/enclaves/list_servers.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ <h3>Assigned RDP Servers</h3>
167167
<th>System Name</th>
168168
<th>Hostname</th>
169169
<th>Host Enclave</th>
170-
<th>User</th>
171170
<th>Operations</th>
172171
</tr>
173172
</thead>
@@ -626,8 +625,7 @@ <h5 class="modal-title" id="rdpModalLabel">RDP Session - <span id="rdpHostName">
626625
{ data: 'displayName' },
627626
{ data: 'displayName' },
628627
{ data: 'group.displayName' },
629-
{ data: 'rdpUser' },
630-
{
628+
{
631629
data: null,
632630
render: function(data, type, row) {
633631
const groupId = row.group ? row.group.groupId : -1; // Access group.id

core/src/main/java/io/sentrius/sso/core/dto/HostSystemDTO.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ public class HostSystemDTO {
2525

2626
private String authorizedKeys;
2727

28-
@Builder.Default
29-
private boolean isRdp = false;
30-
private boolean rdpUser;
31-
private boolean rdpPassword;
28+
private boolean isRdp;
29+
private String rdpUser;
30+
private String rdpPassword;
3231

3332
}

core/src/main/java/io/sentrius/sso/core/services/security/JwtUtil.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package io.sentrius.sso.core.services.security;
22

33
import java.nio.charset.StandardCharsets;
4+
import java.util.ArrayList;
45
import java.util.Base64;
6+
import java.util.List;
57
import java.util.Optional;
68
import com.fasterxml.jackson.core.JsonProcessingException;
9+
import com.fasterxml.jackson.databind.JsonNode;
710
import com.fasterxml.jackson.databind.node.ObjectNode;
811
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
912
import io.jsonwebtoken.Jwts;
@@ -88,6 +91,19 @@ public static Optional<String> getUserTypeName(ObjectNode jwt) {
8891

8992
}
9093

94+
public static Optional<List<String>> getGroups(ObjectNode jwt) {
95+
return Optional.ofNullable(jwt.get("claims"))
96+
.map(c -> c.get("assignedGroups"))
97+
.filter(JsonNode::isArray)
98+
.map(arr -> {
99+
List<String> groups = new ArrayList<>();
100+
arr.forEach(n -> groups.add(n.asText()));
101+
return groups;
102+
});
103+
}
104+
105+
106+
91107
public static String extractKid(String jwt) {
92108
try {
93109
// Strip "Bearer " prefix if present

dataplane/src/main/java/io/sentrius/sso/core/model/HostSystem.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ public HostSystemDTO toDTO() {
153153
dto.statusCd(this.statusCd);
154154
if (rdpEnabled) {
155155
dto.isRdp(true);
156-
dto.rdpUser(rdpUser != null && !rdpUser.isEmpty());
157-
dto.rdpPassword(rdpPassword != null && !rdpPassword.isEmpty());
156+
dto.rdpUser(rdpUser);
157+
dto.rdpPassword(rdpPassword);
158158
}
159159
dto.publicKeyList(this.publicKeyList != null ? new ArrayList<>(this.publicKeyList) : new ArrayList<>());
160160
dto.errorMsg(this.errorMsg);
@@ -175,8 +175,8 @@ public HostSystemDTO toDTO(HostGroup hg) {
175175
dto.statusCd(this.statusCd);
176176
if (rdpEnabled) {
177177
dto.isRdp(true);
178-
dto.rdpUser(rdpUser != null && !rdpUser.isEmpty());
179-
dto.rdpPassword(rdpPassword != null && !rdpPassword.isEmpty());
178+
dto.rdpUser(rdpUser);
179+
dto.rdpPassword(rdpPassword);
180180
}
181181
dto.publicKeyList(this.publicKeyList != null ? new ArrayList<>(this.publicKeyList) : new ArrayList<>());
182182
dto.errorMsg(this.errorMsg);
@@ -209,12 +209,11 @@ public static HostSystem fromDTO(HostSystemDTO dto) {
209209
hostSystem.setErrorMsg(dto.getErrorMsg());
210210
hostSystem.setPort(dto.getPort());
211211
hostSystem.setSshUser(dto.getSshUser());
212-
if (dto.isRdp()) {
213-
hostSystem.setRdpEnabled(dto.isRdp());
214-
hostSystem.setRdpUser(dto.isRdpUser() ? dto.getSshUser() : "");
215-
hostSystem.setRdpPassword(dto.isRdpPassword() ? dto.getPassword() : "");
212+
if (dto.isRdp()){
213+
hostSystem.setRdpEnabled(true);
214+
hostSystem.setRdpUser(dto.getRdpUser());
215+
hostSystem.setRdpPassword(dto.getRdpPassword());
216216
}
217-
218217
return hostSystem;
219218
}
220219

dataplane/src/main/java/io/sentrius/sso/core/repository/ProfileRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.sentrius.sso.core.repository;
22

33
import java.util.List;
4+
import java.util.Optional;
45
import io.sentrius.sso.core.model.hostgroup.HostGroup;
56
import org.springframework.data.jpa.repository.JpaRepository;
67
import org.springframework.stereotype.Repository;
@@ -11,4 +12,8 @@ public interface ProfileRepository extends JpaRepository<HostGroup, Long> {
1112
HostGroup getById(Long profileId);
1213

1314
List<HostGroup> findAll();
15+
16+
Optional<HostGroup> findByName(String name);
17+
18+
1419
}

dataplane/src/main/java/io/sentrius/sso/core/services/UserService.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ public User getOperatingUser(HttpServletRequest request, HttpServletResponse res
160160
.build();
161161
ProfileDB.save(newHg);
162162

163+
Optional<List<String>> assignedGroups = JwtUtil.getGroups(jwt);
164+
if (assignedGroups.isPresent()) {
165+
for(String groupName : assignedGroups.get()) {
166+
Optional<HostGroup> hg = ProfileDB.findByName(groupName);
167+
if (hg.isPresent()) {
168+
operatingUser.getHostGroups().add(hg.get());
169+
}
170+
}
171+
}
172+
163173
operatingUser.getHostGroups().add(newHg);
164174
save(operatingUser);
165175
} else {

0 commit comments

Comments
 (0)