diff --git a/.gcp.env b/.gcp.env index 70175931..baad2ae3 100644 --- a/.gcp.env +++ b/.gcp.env @@ -1,4 +1,4 @@ -SENTRIUS_VERSION=1.0.23 -SENTRIUS_SSH_VERSION=1.0.2 +SENTRIUS_VERSION=1.0.33 +SENTRIUS_SSH_VERSION=1.0.3 SENTRIUS_KEYCLOAK_VERSION=1.0.4 SENTRIUS_AGENT_VERSION=1.0.14 \ No newline at end of file diff --git a/api/src/main/java/io/sentrius/sso/controllers/api/EnclaveApiController.java b/api/src/main/java/io/sentrius/sso/controllers/api/EnclaveApiController.java index 798c74d4..8c1a5418 100644 --- a/api/src/main/java/io/sentrius/sso/controllers/api/EnclaveApiController.java +++ b/api/src/main/java/io/sentrius/sso/controllers/api/EnclaveApiController.java @@ -154,6 +154,8 @@ public String editEnclave(HttpServletRequest request, HttpServletResponse respon String maxConcurrentSessions = request.getParameter("maxConcurrentSessions"); String allowSudoStr = request.getParameter("allowSudo"); String approveViaTicketStr = request.getParameter("approveViaTicket"); + + String autoApproveChangingHostKey = request.getParameter("autoApproveChangingHostKey"); log.info("allowSudoStr: {}", allowSudoStr); var hostGroup = hg.get(); @@ -176,6 +178,15 @@ public String editEnclave(HttpServletRequest request, HttpServletResponse respon configuration.setApproveViaTicket(false); } + if (null != autoApproveChangingHostKey && !autoApproveChangingHostKey.isEmpty()) { + log.info("Setting autoApproveChangingHostKey to true {}", autoApproveChangingHostKey); + configuration.setAutoApproveChangingHostKey(true); + } + else { + log.info("Setting autoApproveChangingHostKey to false {}", autoApproveChangingHostKey); + configuration.setAutoApproveChangingHostKey(false); + } + hostGroup.setName(displayName); hostGroup.setDescription(description); hostGroup.setConfiguration(configuration); diff --git a/api/src/main/java/io/sentrius/sso/controllers/api/UserApiController.java b/api/src/main/java/io/sentrius/sso/controllers/api/UserApiController.java index 74eb0d0d..e0cd31ef 100644 --- a/api/src/main/java/io/sentrius/sso/controllers/api/UserApiController.java +++ b/api/src/main/java/io/sentrius/sso/controllers/api/UserApiController.java @@ -22,6 +22,7 @@ import io.sentrius.sso.core.model.users.UserSettings; import io.sentrius.sso.core.security.service.CryptoService; import io.sentrius.sso.core.services.ErrorOutputService; +import io.sentrius.sso.core.services.SessionService; import io.sentrius.sso.core.services.UserCustomizationService; import io.sentrius.sso.core.services.UserService; import io.sentrius.sso.core.services.HostGroupService; @@ -58,17 +59,21 @@ public class UserApiController extends BaseController { } } + private final SessionService sessionService; + protected UserApiController(UserService userService, SystemOptions systemOptions, ErrorOutputService errorOutputService, HostGroupService hostGroupService, CryptoService cryptoService, MessagingUtil messagingUtil, - UserCustomizationService userThemeService + UserCustomizationService userThemeService, + SessionService sessionService ) { super(userService, systemOptions, errorOutputService); this.hostGroupService = hostGroupService; this.cryptoService = cryptoService; this.messagingUtil = messagingUtil; this.userThemeService = userThemeService; + this.sessionService = sessionService; } @GetMapping("list") @@ -208,5 +213,14 @@ public String deleteType(@RequestParam("id") String dtoId) throws GeneralSecurit return "redirect:/sso/v1/users/list?message=" + MessagingUtil.getMessageId(MessagingUtil.USER_DELETE_SUCCESS); } + @GetMapping("/sessions/graph") + public ResponseEntity> getGraphData(HttpServletRequest request, + HttpServletResponse response) { + var username = userService.getOperatingUser(request,response, null).getUsername(); + var ret= sessionService.getGraphData(username); + + return ResponseEntity.ok(ret); + } + } diff --git a/api/src/main/resources/templates/sso/dashboard.html b/api/src/main/resources/templates/sso/dashboard.html index 2bf56c04..b499f50a 100755 --- a/api/src/main/resources/templates/sso/dashboard.html +++ b/api/src/main/resources/templates/sso/dashboard.html @@ -3,6 +3,162 @@ [[${systemOptions.systemLogoName}]] - Dashboard + @@ -191,24 +374,17 @@


-
+
Enclave Operations
@@ -220,19 +396,31 @@
System Operations
-
+ - -
-

Open Sessions

- +
+
+
+
+
User Operations
+
+
@@ -244,7 +432,7 @@
User Operations
-
+
Delete Users Add User @@ -253,17 +441,56 @@
User Operations
View JITs
+
+
+
+
+

Terminal Stats

+
+ +
+
+
+
Most Used Commands
+
+
+ +
+
+
+
+
Open Sessions
+
+
+ +
+
+
+
+
Session Duration Distribution
+
+
+ +
+
- -
-

User Activity

- - +
+
+
Terminal Activity
+
+
+
+
+ +

Open Terminals

@@ -300,10 +527,14 @@

Open Terminals

if (null == document.getElementById('tachometerChart')){ return; } - const ctx = document.getElementById('tachometerChart').getContext('2d'); + var dnt = document.getElementById('tachometerChart'); + dnt.style.width = '250px'; + dnt.style.height = '100px'; + const ctx = dnt.getContext('2d'); if (tachometerChart){ tachometerChart.destroy(); } + tachometerChart = new Chart(ctx, { type: 'doughnut', data: { diff --git a/api/src/main/resources/templates/sso/enclaves/edit_enclave.html b/api/src/main/resources/templates/sso/enclaves/edit_enclave.html index 85566a60..44da6077 100755 --- a/api/src/main/resources/templates/sso/enclaves/edit_enclave.html +++ b/api/src/main/resources/templates/sso/enclaves/edit_enclave.html @@ -153,6 +153,12 @@

Edit Enclave Configuration

th:checked="${hostGroup.configuration.approveViaTicket}" />
+
+ + +
+
diff --git a/api/src/main/resources/templates/sso/ssh/secure_shell.html b/api/src/main/resources/templates/sso/ssh/secure_shell.html index 09e51b5d..b82b6e36 100755 --- a/api/src/main/resources/templates/sso/ssh/secure_shell.html +++ b/api/src/main/resources/templates/sso/ssh/secure_shell.html @@ -439,7 +439,6 @@ ws_uri += "//" + loc.host + "/api/v1/ssh/terminal/subscribe?sessionId=" + encodedSessionId; - connection = new SockJS(ws_uri); function heartbeat() { @@ -456,165 +455,168 @@ const binaryData = auditLog.serializeBinary(); const base64Message = btoa(String.fromCharCode(...binaryData)); connection.send(base64Message); - //connection.send(binaryData); - //connection.send("heartbeat"); // Set heartbeat interval 10,000 ms - setTimeout(heartbeat, 10000); + setTimeout(heartbeat, 5000); }; - // Register websocket heartbeat - connection.onopen = function (e) { - heartbeat(); - }; + function connect() { + connection = new SockJS(ws_uri); + // Register websocket heartbeat + connection.onopen = function (e) { + heartbeat(); + }; - connection.onclose = function (e) { - console.trace(); - }; + connection.onclose = function (e) { + console.trace(); + console.log("Repopening"); + connect(); + }; - // Log errors - connection.onerror = function (error) { - }; + // Log errors + connection.onerror = function (error) { + console.trace(); + }; - - connection.onmessage = function (e) { - try { - const binaryMessage = Uint8Array.from(atob(e.data), c => c.charCodeAt(0)); + connection.onmessage = function (e) { try { - const deserializedAuditLog = proto.io.sentrius.protobuf.TerminalMessage.deserializeBinary(binaryMessage); - if (!deserializedAuditLog ) { - console.warn("Malformed message received"); - return; - } - if (deserializedAuditLog.getType() == proto.io.sentrius.protobuf.MessageType.USER_DATA) { - - const alertWarn = document.getElementById("alertWarn"); - if (!deserializedAuditLog.getTrigger() || deserializedAuditLog.getTrigger() == proto.io.sentrius.protobuf.TriggerAction.NO_ACTION) { - console.log("only command"); - if (!deserializedAuditLog.getCommand()) { - console.warn("No content message "); - return; - } - if (!term) { - - createTermMap(0, deserializedAuditLog.getCommand()); - } else { + const binaryMessage = Uint8Array.from(atob(e.data), c => c.charCodeAt(0)); + try { + const deserializedAuditLog = proto.io.sentrius.protobuf.TerminalMessage.deserializeBinary(binaryMessage); + if (!deserializedAuditLog) { + console.warn("Malformed message received"); + return; + } + if (deserializedAuditLog.getType() == proto.io.sentrius.protobuf.MessageType.USER_DATA) { + + const alertWarn = document.getElementById("alertWarn"); + if (!deserializedAuditLog.getTrigger() || deserializedAuditLog.getTrigger() == proto.io.sentrius.protobuf.TriggerAction.NO_ACTION) { + console.log("only command"); + if (!deserializedAuditLog.getCommand()) { + console.warn("No content message "); + return; + } + if (!term) { - term.write(deserializedAuditLog.getCommand()); - } + createTermMap(0, deserializedAuditLog.getCommand()); + } else { - } else { + term.write(deserializedAuditLog.getCommand()); + } - if (alertWarn) { - const triggerAction = deserializedAuditLog.getTrigger(); - const description = triggerAction.getDescription(); - if (deserializedAuditLog.getType() == - proto.io.sentrius.protobuf.MessageType.PERSISTENT_MESSAGE || - triggerAction.getAction() == - proto.io.sentrius.protobuf.TriggerAction.PERSISTENT_MESSAGE) { - console.log("persistent message"); - persistentMessage = description; - showWarningStatus(description); - } else if (deserializedAuditLog.getType() == - proto.io.sentrius.protobuf.MessageType.PROMPT_DATA) { - console.log("question is" + deserializedAuditLog.getPrompt()); - persistentMessage = description; - showWarningStatus(description); - promptQuestion(deserializedAuditLog.getPrompt()); + } else { - } else { - console.log("non-persistent message " + description + " " + triggerAction.getAction()); - console.log("is no action " + (triggerAction.getAction() == - proto.io.sentrius.protobuf.TriggerAction.NO_ACTION)); - //alertWarn.textContent = description || persistentMessage || ""; - if (description) { - if (triggerAction.getAction() == proto.io.sentrius.protobuf.TriggerAction.WARN_ACTION) { - showWarningStatus(description); - } - else { - showStatusBar(description); + if (alertWarn) { + const triggerAction = deserializedAuditLog.getTrigger(); + const description = triggerAction.getDescription(); + if (deserializedAuditLog.getType() == + proto.io.sentrius.protobuf.MessageType.PERSISTENT_MESSAGE || + triggerAction.getAction() == + proto.io.sentrius.protobuf.TriggerAction.PERSISTENT_MESSAGE) { + console.log("persistent message"); + persistentMessage = description; + showWarningStatus(description); + } else if (deserializedAuditLog.getType() == + proto.io.sentrius.protobuf.MessageType.PROMPT_DATA) { + console.log("question is" + deserializedAuditLog.getPrompt()); + persistentMessage = description; + showWarningStatus(description); + promptQuestion(deserializedAuditLog.getPrompt()); + + } else { + console.log("non-persistent message " + description + " " + triggerAction.getAction()); + console.log("is no action " + (triggerAction.getAction() == + proto.io.sentrius.protobuf.TriggerAction.NO_ACTION)); + //alertWarn.textContent = description || persistentMessage || ""; + if (description) { + if (triggerAction.getAction() == proto.io.sentrius.protobuf.TriggerAction.WARN_ACTION) { + showWarningStatus(description); + } else { + showStatusBar(description); + } } } + // Use textContent instead of innerHTML } - // Use textContent instead of innerHTML + /* if (json.hasOwnProperty("action")){ + document.getElementById("alertWarn").innerHTML = json.description; + } + else if (json.hasOwnProperty("persistentMessage")){ + persistentMessage = json.description; + document.getElementById("alertWarn").innerHTML = json.description; + }* + + */ } - /* if (json.hasOwnProperty("action")){ - document.getElementById("alertWarn").innerHTML = json.description; - } - else if (json.hasOwnProperty("persistentMessage")){ - persistentMessage = json.description; - document.getElementById("alertWarn").innerHTML = json.description; - }* - - */ - } - } else if (deserializedAuditLog.getType() == proto.io.sentrius.protobuf.MessageType.SESSION_DATA) { - if (deserializedAuditLog.getTrigger()) { - const triggerAction = deserializedAuditLog.getTrigger(); - const description = triggerAction.getDescription(); - const overlay = document.getElementById("overlay"); + } else if (deserializedAuditLog.getType() == proto.io.sentrius.protobuf.MessageType.SESSION_DATA) { + if (deserializedAuditLog.getTrigger()) { + const triggerAction = deserializedAuditLog.getTrigger(); + const description = triggerAction.getDescription(); + const overlay = document.getElementById("overlay"); - // Function to show the spinner - function showSpinner() { - overlay.classList.remove("hidden"); - } + // Function to show the spinner + function showSpinner() { + overlay.classList.remove("hidden"); + } - // Function to hide the spinner - function hideSpinner() { - overlay.classList.add("hidden"); - } + // Function to hide the spinner + function hideSpinner() { + overlay.classList.add("hidden"); + } - overlay.innerText = description; + overlay.innerText = description; - if (triggerAction.getAction() == proto.io.sentrius.protobuf.TriggerAction.JIT_ACTION) { - showSpinner(); - } else { - hideSpinner(); - if (triggerAction.getAction() == proto.io.sentrius.protobuf.TriggerAction.WARN_ACTION) { - console.log("WARN_ACTION"); - if (description) { - console.log("WARN_ACTION " + description); - showWarningStatus(description); + if (triggerAction.getAction() == proto.io.sentrius.protobuf.TriggerAction.JIT_ACTION) { + showSpinner(); + } else { + hideSpinner(); + if (triggerAction.getAction() == proto.io.sentrius.protobuf.TriggerAction.WARN_ACTION) { + console.log("WARN_ACTION"); + if (description) { + console.log("WARN_ACTION " + description); + showWarningStatus(description); + } } } + } + } else if (deserializedAuditLog.getType() == + proto.io.sentrius.protobuf.MessageType.PROMPT_DATA) { + console.log("question"); + const triggerAction = deserializedAuditLog.getTrigger(); + const description = triggerAction.getDescription(); + console.log("question is" + deserializedAuditLog.getPrompt()); + persistentMessage = description; + showWarningStatus(description); + promptQuestion(deserializedAuditLog.getPrompt()); + } else { + console.warn("Unknown message type received:", deserializedAuditLog.getType()); } - } else if (deserializedAuditLog.getType() == - proto.io.sentrius.protobuf.MessageType.PROMPT_DATA) { - console.log("question"); - const triggerAction = deserializedAuditLog.getTrigger(); - const description = triggerAction.getDescription(); - console.log("question is" + deserializedAuditLog.getPrompt()); - persistentMessage = description; - showWarningStatus(description); - promptQuestion(deserializedAuditLog.getPrompt()); - - } else { - console.warn("Unknown message type received:", deserializedAuditLog.getType()); + // Proceed with normal processing... + } catch (error) { + console.error("Failed to deserialize message:", error); } - // Proceed with normal processing... - } catch (error) { - console.error("Failed to deserialize message:", error); - } + } catch (error) { + console.error("Failed to deserialize Protocol Buffer message:", error); + console.error("Error details:", { + message: error.message, + stack: error.stack, + name: error.name, + binaryMessage: binaryMessage, // Log the problematic Uint8Array + base64Data: e.data // Log the original base64 data + }); + return; // Exit if decoding failed + } + }; + } - } catch (error) { - console.error("Failed to deserialize Protocol Buffer message:", error); - console.error("Error details:", { - message: error.message, - stack: error.stack, - name: error.name, - binaryMessage: binaryMessage, // Log the problematic Uint8Array - base64Data: e.data // Log the original base64 data - }); - return; // Exit if decoding failed - } - }; + connect(); function createTermMap(id, output) { diff --git a/core/src/main/java/io/sentrius/sso/automation/auditing/rules/SudoApproval.java b/core/src/main/java/io/sentrius/sso/automation/auditing/rules/SudoApproval.java new file mode 100644 index 00000000..cd6d94f9 --- /dev/null +++ b/core/src/main/java/io/sentrius/sso/automation/auditing/rules/SudoApproval.java @@ -0,0 +1,22 @@ +package io.sentrius.sso.automation.auditing.rules; + +import java.util.Optional; +import java.util.regex.Pattern; +import io.sentrius.sso.automation.auditing.Trigger; +import io.sentrius.sso.automation.auditing.TriggerAction; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SudoApproval extends SudoPrevention { + + // Updated pattern to match both 'sudo' and 'su' as standalone commands (case-insensitive) + private static final Pattern SUDO_SU_PATTERN = Pattern.compile("\\b(sudo|su)\\b", Pattern.CASE_INSENSITIVE); + + public SudoApproval() { + super(); + action = TriggerAction.JIT_ACTION; + message = "SUDO and SU require approval."; + isSanitized = true; + } + +} diff --git a/core/src/main/java/io/sentrius/sso/automation/auditing/rules/SudoPrevention.java b/core/src/main/java/io/sentrius/sso/automation/auditing/rules/SudoPrevention.java index 281009d5..fbc29779 100644 --- a/core/src/main/java/io/sentrius/sso/automation/auditing/rules/SudoPrevention.java +++ b/core/src/main/java/io/sentrius/sso/automation/auditing/rules/SudoPrevention.java @@ -14,6 +14,7 @@ public class SudoPrevention extends CommandEvaluator { // Updated pattern to match both 'sudo' and 'su' as standalone commands (case-insensitive) private static final Pattern SUDO_SU_PATTERN = Pattern.compile("\\b(sudo|su)\\b", Pattern.CASE_INSENSITIVE); + protected String message = "SUDO and SU are not allowed"; public SudoPrevention() { action = TriggerAction.DENY_ACTION; isSanitized = true; @@ -29,7 +30,7 @@ public Optional trigger(String text) { if (SUDO_SU_PATTERN.matcher(text).find()) { // Log the blocked attempt log.info("Blocked SUDO/SU attempt: {}", text); - return Optional.of(new Trigger(action, "SUDO and SU are not allowed")); + return Optional.of(new Trigger(action, message)); } return Optional.empty(); diff --git a/core/src/main/java/io/sentrius/sso/core/model/hostgroup/ProfileConfiguration.java b/core/src/main/java/io/sentrius/sso/core/model/hostgroup/ProfileConfiguration.java index f7d3bdb6..1bfa2333 100755 --- a/core/src/main/java/io/sentrius/sso/core/model/hostgroup/ProfileConfiguration.java +++ b/core/src/main/java/io/sentrius/sso/core/model/hostgroup/ProfileConfiguration.java @@ -61,6 +61,9 @@ public class ProfileConfiguration { @Builder.Default private Boolean approveViaTicket = true; + @Builder.Default + private Boolean autoApproveChangingHostKey = false; + @Builder.Default private List sessionRules = new ArrayList<>(); diff --git a/core/src/main/java/io/sentrius/sso/core/repository/SessionLogRepository.java b/core/src/main/java/io/sentrius/sso/core/repository/SessionLogRepository.java index b223e066..9b4510a9 100644 --- a/core/src/main/java/io/sentrius/sso/core/repository/SessionLogRepository.java +++ b/core/src/main/java/io/sentrius/sso/core/repository/SessionLogRepository.java @@ -12,4 +12,6 @@ public interface SessionLogRepository extends JpaRepository { @Query("SELECT DISTINCT t FROM SessionLog t order by t.sessionTm desc") List findUniqueSessionIds(); + + List findByUsername(String username); } diff --git a/core/src/main/java/io/sentrius/sso/core/repository/TerminalLogRepository.java b/core/src/main/java/io/sentrius/sso/core/repository/TerminalLogRepository.java index d2f896d6..f0761dbf 100644 --- a/core/src/main/java/io/sentrius/sso/core/repository/TerminalLogRepository.java +++ b/core/src/main/java/io/sentrius/sso/core/repository/TerminalLogRepository.java @@ -19,4 +19,7 @@ public interface TerminalLogRepository extends JpaRepository "WHERE (:username IS NULL OR t.username = :username)") List findOutputSizeByUserOrAll(@Param("username") String username); + @Query("SELECT MIN(t.logTm), MAX(t.logTm) FROM TerminalLogs t WHERE t.session.id = :sessionLogId") + List findMinAndMaxLogTmBySessionLogId(@Param("sessionLogId") Long sessionLogId); + } diff --git a/core/src/main/java/io/sentrius/sso/core/repository/TerminalLogsRepository.java b/core/src/main/java/io/sentrius/sso/core/repository/TerminalLogsRepository.java index 446f4b23..b8fcbb45 100644 --- a/core/src/main/java/io/sentrius/sso/core/repository/TerminalLogsRepository.java +++ b/core/src/main/java/io/sentrius/sso/core/repository/TerminalLogsRepository.java @@ -4,6 +4,7 @@ import io.sentrius.sso.core.model.sessions.TerminalLogs; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository @@ -12,4 +13,8 @@ public interface TerminalLogsRepository extends JpaRepository findUniqueSessionIds(); + + @Query("SELECT MIN(t.logTm), MAX(t.logTm) FROM TerminalLogs t WHERE t.session.id = :sessionLogId") + List findMinAndMaxLogTmBySessionLogId(@Param("sessionLogId") Long sessionLogId); + } diff --git a/core/src/main/java/io/sentrius/sso/core/services/KnownHostService.java b/core/src/main/java/io/sentrius/sso/core/services/KnownHostService.java index 94bf8243..95522d1d 100644 --- a/core/src/main/java/io/sentrius/sso/core/services/KnownHostService.java +++ b/core/src/main/java/io/sentrius/sso/core/services/KnownHostService.java @@ -32,13 +32,20 @@ public KnownHostService(KnownHostRepository knownHostRepository) throws IOExcept @Transactional public void saveHostKey(String hostname, String keyType, String keyValue) { - if (knownHostRepository.findByHostnameAndKeyType(hostname, keyType) == null) { - KnownHost knownHost = new KnownHost(); + KnownHost knownHost = knownHostRepository.findByHostnameAndKeyType(hostname, keyType); + if (knownHost== null) { + knownHost = new KnownHost(); knownHost.setHostname(hostname); knownHost.setKeyType(keyType); knownHost.setKeyValue(keyValue); knownHostRepository.save(knownHost); + // Rebuild the file after adding a new host key + rebuildKnownHostsFile(); + } else { + knownHost.setKeyValue(keyValue); + knownHostRepository.save(knownHost); + // Rebuild the file after adding a new host key rebuildKnownHostsFile(); } diff --git a/core/src/main/java/io/sentrius/sso/core/services/SessionService.java b/core/src/main/java/io/sentrius/sso/core/services/SessionService.java index 26b71b6b..b914c9d8 100644 --- a/core/src/main/java/io/sentrius/sso/core/services/SessionService.java +++ b/core/src/main/java/io/sentrius/sso/core/services/SessionService.java @@ -14,6 +14,10 @@ import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -104,4 +108,55 @@ public List getLogOutputSummary(String username) { return terminalLogRepository.findOutputSizeByUserOrAll(username); } + public List> getSessionDurationData(String username) { + List sessionLogs = sessionLogRepository.findByUsername(username); + List> sessionDurations = new ArrayList<>(); + + for (SessionLog sessionLog : sessionLogs) { + List minMaxLogTm = terminalLogRepository.findMinAndMaxLogTmBySessionLogId(sessionLog.getId()); + + if (!minMaxLogTm.isEmpty() && minMaxLogTm.get(0)[0] != null && minMaxLogTm.get(0)[1] != null) { + Timestamp minTimestamp = (Timestamp) minMaxLogTm.get(0)[0]; + Timestamp maxTimestamp = (Timestamp) minMaxLogTm.get(0)[1]; + + LocalDateTime minLogTm = minTimestamp.toLocalDateTime(); + LocalDateTime maxLogTm = maxTimestamp.toLocalDateTime(); + long durationMinutes = ChronoUnit.MINUTES.between(minLogTm, maxLogTm); + + sessionDurations.add(Map.of( + "sessionId", sessionLog.getId(), + "durationMinutes", durationMinutes + )); + } + } + + return sessionDurations; + } + + public Map getGraphData(String username) { + List> sessionDurations = getSessionDurationData(username); + + Map graphData = new HashMap<>(); + graphData.put("0-5 min", 0); + graphData.put("5-15 min", 0); + graphData.put("15-30 min", 0); + graphData.put("30+ min", 0); + + for (Map session : sessionDurations) { + long durationMinutes = (long) session.get("durationMinutes"); + + if (durationMinutes <= 5) { + graphData.put("0-5 min", graphData.get("0-5 min") + 1); + } else if (durationMinutes <= 15) { + graphData.put("5-15 min", graphData.get("5-15 min") + 1); + } else if (durationMinutes <= 30) { + graphData.put("15-30 min", graphData.get("15-30 min") + 1); + } else { + graphData.put("30+ min", graphData.get("30+ min") + 1); + } + } + + return graphData; + } + } \ No newline at end of file diff --git a/core/src/main/java/io/sentrius/sso/core/services/TerminalService.java b/core/src/main/java/io/sentrius/sso/core/services/TerminalService.java index 638f234e..c9f092fa 100644 --- a/core/src/main/java/io/sentrius/sso/core/services/TerminalService.java +++ b/core/src/main/java/io/sentrius/sso/core/services/TerminalService.java @@ -272,7 +272,7 @@ public ConnectedSystem openTerminal( ex.printStackTrace(); schSession.getHostSystem().setStatusCd(HostSystem.AUTH_FAIL_STATUS); } else if (ex.getMessage().toLowerCase().contains("reject hostkey")) { - // we need to add the host key to known hosts + // host key has been changed. if (!fetchedHostKey) { schSession.getHostSystem().setStatusCd(HostSystem.PUBLIC_KEY_FAIL_STATUS); systemRepository.save(schSession.getHostSystem()); @@ -296,8 +296,37 @@ public ConnectedSystem openTerminal( } else { schSession.getHostSystem().setStatusCd(HostSystem.PUBLIC_KEY_FAIL_STATUS); } - - + } else if (ex.getMessage().toLowerCase().contains("hostkey has been changed")) { + if (enclave.getConfiguration().getAutoApproveChangingHostKey()) { + // we need to add the host key to known hosts + if (!fetchedHostKey) { + schSession.getHostSystem().setStatusCd(HostSystem.PUBLIC_KEY_FAIL_STATUS); + systemRepository.save(schSession.getHostSystem()); + log.info("Failed to connect to system: " + schSession.getHostSystem().getDisplayName()); + // remove the tracking + sessionOutputService.removeOutput(schSession); + //sessionService.closeSession(sessionLog); + ApplicationKey appKey = null; + try { + appKey = keyStoreService.getGlobalKey(); + } catch (JSchException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + fetchHostKey(selectedSystem, appKey, passphrase, password); + + return openTerminal(user, sessionLog, enclave, passphrase, password, selectedSystem, + sessionRules, true + ); + } else { + log.info("Host key has changed and auto approve is not enabled 2"); + schSession.getHostSystem().setStatusCd(HostSystem.PUBLIC_KEY_FAIL_STATUS); + } + } else { + log.info("Host key has changed and auto approve is not enabled"); + schSession.getHostSystem().setStatusCd(HostSystem.PUBLIC_KEY_FAIL_STATUS); + } } else if (ex.getMessage().toLowerCase().contains("unknownhostexception")) { schSession.getHostSystem().setErrorMsg("DNS Lookup Failed"); schSession.getHostSystem().setStatusCd(HostSystem.HOST_FAIL_STATUS); diff --git a/docker/fake-ssh/Dockerfile b/docker/fake-ssh/Dockerfile index 0468a2c3..afa9656d 100644 --- a/docker/fake-ssh/Dockerfile +++ b/docker/fake-ssh/Dockerfile @@ -1,7 +1,7 @@ FROM public.ecr.aws/lts/ubuntu:20.04 RUN apt-get update \ - && apt-get install -y openssh-server net-tools iputils-ping sshpass \ + && apt-get install -y openssh-server net-tools iputils-ping sshpass sudo \ && mkdir /var/run/sshd \ && mkdir /root/.ssh \ && sed -ri 's/^#?Port 22/Port 22/' /etc/ssh/sshd_config \ @@ -11,11 +11,14 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -RUN useradd -ms /bin/bash ubuntu -RUN passwd -d ubuntu +RUN useradd -ms /bin/bash ubuntu \ + && passwd -d ubuntu \ + && echo "ubuntu ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers COPY entrypoint.sh /usr/local/bin/ RUN chmod 755 /usr/local/bin/entrypoint.sh COPY priv_key.pub /home/ubuntu/.ssh/authorized_keys +RUN chown -R ubuntu:ubuntu /home/ubuntu/.ssh + ENTRYPOINT ["entrypoint.sh"] diff --git a/sentrius-gcp-chart/templates/configmap.yaml b/sentrius-gcp-chart/templates/configmap.yaml index 3ab8b67b..5f80a0f0 100644 --- a/sentrius-gcp-chart/templates/configmap.yaml +++ b/sentrius-gcp-chart/templates/configmap.yaml @@ -118,6 +118,7 @@ data: systemLogoName={{ .Values.tenant }} AccessTokenAuditor.rule.4=io.sentrius.sso.automation.auditing.rules.OpenAISessionRule;Malicious AI Monitoring AccessTokenAuditor.rule.5=io.sentrius.sso.automation.auditing.rules.TwoPartyAIMonitor;AI Second Party Monitor + AccessTokenAuditor.rule.6=io.sentrius.sso.automation.auditing.rules.SudoApproval;Root JIT Approval allowProxies=true AccessTokenAuditor.rule.2=io.sentrius.sso.automation.auditing.rules.DeletePrevention;Delete Prevention AccessTokenAuditor.rule.3=io.sentrius.sso.automation.auditing.rules.TwoPartySessionRule;Require Second Party Monitoring