Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .local.env
Original file line number Diff line number Diff line change
@@ -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
RDPPROXY_VERSION=1.0.122
6 changes: 3 additions & 3 deletions .local.env.bak
Original file line number Diff line number Diff line change
@@ -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
RDPPROXY_VERSION=1.0.122
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ public ResponseEntity<Map<String, Object>> initiateRdpSession(

// Validate access to the host group
Optional<HostGroup> 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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<SideEffect> createStaticType(UserType type, boolean action) throws SQLException,
GeneralSecurityException {
Expand Down Expand Up @@ -498,9 +523,11 @@ protected List<SideEffect> 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(
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ <h3>Assigned RDP Servers</h3>
<th>System Name</th>
<th>Hostname</th>
<th>Host Enclave</th>
<th>User</th>
<th>Operations</th>
</tr>
</thead>
Expand Down Expand Up @@ -626,8 +625,7 @@ <h5 class="modal-title" id="rdpModalLabel">RDP Session - <span id="rdpHostName">
{ data: 'displayName' },
{ data: 'displayName' },
{ data: 'group.displayName' },
{ data: 'rdpUser' },
{
{
data: null,
render: function(data, type, row) {
const groupId = row.group ? row.group.groupId : -1; // Access group.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ public class HostSystemDTO {

private String authorizedKeys;

@Builder.Default
private boolean isRdp = false;
private boolean rdpUser;
private boolean rdpPassword;
private boolean isRdp;
private String rdpUser;
private String rdpPassword;

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.sentrius.sso.core.services.security;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.jsonwebtoken.Jwts;
Expand Down Expand Up @@ -88,6 +91,19 @@ public static Optional<String> getUserTypeName(ObjectNode jwt) {

}

public static Optional<List<String>> getGroups(ObjectNode jwt) {
return Optional.ofNullable(jwt.get("claims"))
.map(c -> c.get("assignedGroups"))
.filter(JsonNode::isArray)
.map(arr -> {
List<String> groups = new ArrayList<>();
arr.forEach(n -> groups.add(n.asText()));
return groups;
});
}



public static String extractKid(String jwt) {
try {
// Strip "Bearer " prefix if present
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ public HostSystemDTO toDTO() {
dto.statusCd(this.statusCd);
if (rdpEnabled) {
dto.isRdp(true);
dto.rdpUser(rdpUser != null && !rdpUser.isEmpty());
dto.rdpPassword(rdpPassword != null && !rdpPassword.isEmpty());
dto.rdpUser(rdpUser);
dto.rdpPassword(rdpPassword);
}
dto.publicKeyList(this.publicKeyList != null ? new ArrayList<>(this.publicKeyList) : new ArrayList<>());
dto.errorMsg(this.errorMsg);
Expand All @@ -175,8 +175,8 @@ public HostSystemDTO toDTO(HostGroup hg) {
dto.statusCd(this.statusCd);
if (rdpEnabled) {
dto.isRdp(true);
dto.rdpUser(rdpUser != null && !rdpUser.isEmpty());
dto.rdpPassword(rdpPassword != null && !rdpPassword.isEmpty());
dto.rdpUser(rdpUser);
dto.rdpPassword(rdpPassword);
}
dto.publicKeyList(this.publicKeyList != null ? new ArrayList<>(this.publicKeyList) : new ArrayList<>());
dto.errorMsg(this.errorMsg);
Expand Down Expand Up @@ -209,12 +209,11 @@ public static HostSystem fromDTO(HostSystemDTO dto) {
hostSystem.setErrorMsg(dto.getErrorMsg());
hostSystem.setPort(dto.getPort());
hostSystem.setSshUser(dto.getSshUser());
if (dto.isRdp()) {
hostSystem.setRdpEnabled(dto.isRdp());
hostSystem.setRdpUser(dto.isRdpUser() ? dto.getSshUser() : "");
hostSystem.setRdpPassword(dto.isRdpPassword() ? dto.getPassword() : "");
if (dto.isRdp()){
hostSystem.setRdpEnabled(true);
hostSystem.setRdpUser(dto.getRdpUser());
hostSystem.setRdpPassword(dto.getRdpPassword());
}

return hostSystem;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.sentrius.sso.core.repository;

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

List<HostGroup> findAll();

Optional<HostGroup> findByName(String name);


}
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ public User getOperatingUser(HttpServletRequest request, HttpServletResponse res
.build();
ProfileDB.save(newHg);

Optional<List<String>> assignedGroups = JwtUtil.getGroups(jwt);
if (assignedGroups.isPresent()) {
for(String groupName : assignedGroups.get()) {
Optional<HostGroup> hg = ProfileDB.findByName(groupName);
if (hg.isPresent()) {
operatingUser.getHostGroups().add(hg.get());
}
}
}

operatingUser.getHostGroups().add(newHg);
save(operatingUser);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ public void deleteById(Long id) {
public List<IntegrationSecurityToken> findByConnectionType(String connectionType) {
return repository.findByConnectionType(connectionType).stream().map(token -> {
// decrypt the connecting info
log.info("IntegrationSecurityTokenService.findByConnectionType: {}", token);
IntegrationSecurityToken unmanaged = IntegrationSecurityToken.builder()
.id(token.getId())
.connectionType(token.getConnectionType())
Expand Down
5 changes: 4 additions & 1 deletion docker/keycloak/realms/sentrius-realm.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,10 @@
"enabled": true,
"emailVerified": true,
"attributes": {
"userType": "Full Access"
"userType": "Full Access",
"assignedGroups": [
"testGroup"
]
},
"credentials": [
{
Expand Down
7 changes: 5 additions & 2 deletions docker/sentrius/demoInstaller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ systems:
authorizedKeys: ~/.ssh/authorized_keys
- displayName: sentrius-rdp-test
sshUser: ubuntu
password: ubuntu
port: 3389
host: sentrius-rdp-test
authorizedKeys: ~/.ssh/authorized_keys
rdp: true
host: sentrius-rdp
rdpUser: ubuntu
rdpPassword: ubuntu

## Define groups of users who are assigned to systems
## also entails the configuration that is applied to groupf
Expand Down
Loading
Loading