From d99e2d5d45a468cf789a03ee5fd5ba2d8a8d7eae Mon Sep 17 00:00:00 2001 From: Jake Smith Date: Thu, 12 Dec 2024 16:52:44 +0000 Subject: [PATCH 1/4] Split off 9.4.120 Signed-off-by: Jake Smith --- helm/hpcc/Chart.yaml | 4 ++-- helm/hpcc/templates/_helpers.tpl | 2 +- version.cmake | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helm/hpcc/Chart.yaml b/helm/hpcc/Chart.yaml index cf49e3bc463..faa2a68021b 100644 --- a/helm/hpcc/Chart.yaml +++ b/helm/hpcc/Chart.yaml @@ -6,9 +6,9 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 9.4.119-closedown0 +version: 9.4.121-closedown0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. -appVersion: 9.4.119-closedown0 +appVersion: 9.4.121-closedown0 diff --git a/helm/hpcc/templates/_helpers.tpl b/helm/hpcc/templates/_helpers.tpl index 55f40307bf1..bd96564748c 100644 --- a/helm/hpcc/templates/_helpers.tpl +++ b/helm/hpcc/templates/_helpers.tpl @@ -1473,7 +1473,7 @@ Pass in dict with .root, .visibility defined {{- end -}} {{- define "hpcc.generateHelmVersion" -}} -helmVersion: 9.4.119-closedown0 +helmVersion: 9.4.121-closedown0 {{- end -}} {{/* diff --git a/version.cmake b/version.cmake index 308b4437ad3..052bc287bfa 100644 --- a/version.cmake +++ b/version.cmake @@ -5,8 +5,8 @@ set ( HPCC_NAME "Community Edition" ) set ( HPCC_PROJECT "community" ) set ( HPCC_MAJOR 9 ) set ( HPCC_MINOR 4 ) -set ( HPCC_POINT 119 ) +set ( HPCC_POINT 121 ) set ( HPCC_MATURITY "closedown" ) set ( HPCC_SEQUENCE 0 ) -set ( HPCC_TAG_TIMESTAMP "2024-12-03T16:45:21Z" ) +set ( HPCC_TAG_TIMESTAMP "2024-12-12T16:52:44Z" ) ### From d7baff74eddb8800a652b835b6668909744a1fe7 Mon Sep 17 00:00:00 2001 From: Jake Smith Date: Thu, 12 Dec 2024 16:54:39 +0000 Subject: [PATCH 2/4] Split off 9.2.146 Signed-off-by: Jake Smith --- helm/hpcc/Chart.yaml | 4 ++-- helm/hpcc/templates/_helpers.tpl | 2 +- version.cmake | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helm/hpcc/Chart.yaml b/helm/hpcc/Chart.yaml index 369f1bb9e7d..0ccfa015a36 100644 --- a/helm/hpcc/Chart.yaml +++ b/helm/hpcc/Chart.yaml @@ -6,9 +6,9 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 9.2.145-closedown0 +version: 9.2.147-closedown0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. -appVersion: 9.2.145-closedown0 +appVersion: 9.2.147-closedown0 diff --git a/helm/hpcc/templates/_helpers.tpl b/helm/hpcc/templates/_helpers.tpl index 346248ace28..5b0a7cf6169 100644 --- a/helm/hpcc/templates/_helpers.tpl +++ b/helm/hpcc/templates/_helpers.tpl @@ -1361,7 +1361,7 @@ Pass in dict with .root, .visibility defined {{- end -}} {{- define "hpcc.generateHelmVersion" -}} -helmVersion: 9.2.145-closedown0 +helmVersion: 9.2.147-closedown0 {{- end -}} {{/* diff --git a/version.cmake b/version.cmake index 0b8524e2c9a..55cb65d87e4 100644 --- a/version.cmake +++ b/version.cmake @@ -5,8 +5,8 @@ set ( HPCC_NAME "Community Edition" ) set ( HPCC_PROJECT "community" ) set ( HPCC_MAJOR 9 ) set ( HPCC_MINOR 2 ) -set ( HPCC_POINT 145 ) +set ( HPCC_POINT 147 ) set ( HPCC_MATURITY "closedown" ) set ( HPCC_SEQUENCE 0 ) -set ( HPCC_TAG_TIMESTAMP "2024-12-03T16:46:43Z" ) +set ( HPCC_TAG_TIMESTAMP "2024-12-12T16:54:39Z" ) ### From 1c60d2e917f1e62579346de21dbb448ce9970cba Mon Sep 17 00:00:00 2001 From: Terrence Asselin Date: Fri, 6 Dec 2024 14:47:18 -0600 Subject: [PATCH 3/4] HPCC-32999 Sanitize user provided password to ZAP file - Single-quote the entire password to preserve all characters but prevent interpretation as metacharacters. - Replace any single quote in the original string with a sequence of characters that breaks the single-quoted password into three parts (1) a single-quoted prefix (2) a double-quoted single quote and (3) a single-quoted suffix - The shell quote-removal deletes double-quotes around any single quote at the same time that it de-quotes the single-quoted prefix and suffix, leaving any single quote inside the password string that is tokenized as a single argument. This will require a different approach for Windows targets as a future task. Signed-off-by: Terrence Asselin --- .../ws_workunits/ws_workunitsHelpers.cpp | 6 +++- system/jlib/jstring.cpp | 29 +++++++++++++++++++ system/jlib/jstring.hpp | 3 ++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/esp/services/ws_workunits/ws_workunitsHelpers.cpp b/esp/services/ws_workunits/ws_workunitsHelpers.cpp index ad23470fe64..0059fe52bd8 100644 --- a/esp/services/ws_workunits/ws_workunitsHelpers.cpp +++ b/esp/services/ws_workunits/ws_workunitsHelpers.cpp @@ -4386,7 +4386,11 @@ void CWsWuFileHelper::zipZAPFiles(const char* parentFolder, const char* zapFiles else zipCommand.setf("cd %s\nzip -r", parentFolder); if (!isEmptyString(passwordReq)) - zipCommand.append(" --password ").append(passwordReq); + { + StringBuffer sanitizedPassword; + sanitizeCommandArg(passwordReq, sanitizedPassword); + zipCommand.append(" --password ").append(sanitizedPassword); + } zipCommand.append(" ").append(zipFileNameWithFullPath).append(" ").append(zapFiles); int zipRet = system(zipCommand); if (zipRet != 0) diff --git a/system/jlib/jstring.cpp b/system/jlib/jstring.cpp index 930ded8801e..e08e62066ef 100644 --- a/system/jlib/jstring.cpp +++ b/system/jlib/jstring.cpp @@ -2902,3 +2902,32 @@ const char * stristr (const char *haystack, const char *needle) return nullptr; } + +/** + * For preventing command injection, sanitize the argument to be passed to the system command. + * - Quote the entire argument with single quotes to prevent interpretation of shell metacharacters. + * - Since a single-quoted string can't contain single quotes, even escaped, replace each single + * quote in the argument with the sequence '"'"' . That closes the single quoted string, appends + * a literal single quote, and reopens the single quoted string + */ +StringBuffer& sanitizeCommandArg(const char* arg, StringBuffer& sanitized) +{ +#if defined(__linux__) || defined(__APPLE__) + if (!isEmptyString(arg)) + { + size_t len = strlen(arg); + sanitized.append('\''); + for (size_t i = 0; i < len; i++) + { + if (arg[i] == '\'') + sanitized.append(R"('"'"')"); + else + sanitized.append(arg[i]); + } + sanitized.append('\''); + } +#else + sanitized.append(arg); +#endif + return sanitized; +} diff --git a/system/jlib/jstring.hpp b/system/jlib/jstring.hpp index 162679a9e13..ebc90147db7 100644 --- a/system/jlib/jstring.hpp +++ b/system/jlib/jstring.hpp @@ -641,4 +641,7 @@ extern jlib_decl void processOptionString(const char * options, optionCallback c extern jlib_decl const char * stristr(const char *haystack, const char *needle); +// For preventing command injection, sanitize the argument to be passed to the system command +extern jlib_decl StringBuffer& sanitizeCommandArg(const char* arg, StringBuffer& sanitized); + #endif From e2022835201a70d73f79a91e075aa90b4053cb8d Mon Sep 17 00:00:00 2001 From: Jeremy Clements <79224539+jeclrsg@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:50:07 -0500 Subject: [PATCH 4/4] HPCC-32861 ECL Watch v9 WU logs tab tooltip Adds a tooltip to each of the tabs on WU (and File and Query) details pages. The tooltip defaults to just displaying the tab label. But in the case of the WU logs tab, the tooltip will display any reason reported by ESP as to why logging is unavailable. Signed-off-by: Jeremy Clements <79224539+jeclrsg@users.noreply.github.com> --- esp/src/src-react/components/Logs.tsx | 2 +- esp/src/src-react/components/Menu.tsx | 12 +++---- .../src-react/components/WorkunitDetails.tsx | 17 +++------- .../controls/TabbedPanes/OverflowTabList.tsx | 6 ++-- .../controls/TabbedPanes/TabInfo.ts | 1 + esp/src/src-react/hooks/platform.ts | 34 ++++++++++++++----- esp/src/src/nls/hpcc.ts | 3 ++ 7 files changed, 43 insertions(+), 32 deletions(-) diff --git a/esp/src/src-react/components/Logs.tsx b/esp/src/src-react/components/Logs.tsx index 02fcd0e80a6..321d54fd330 100644 --- a/esp/src/src-react/components/Logs.tsx +++ b/esp/src/src-react/components/Logs.tsx @@ -112,7 +112,7 @@ export const Logs: React.FunctionComponent = ({ const now = React.useMemo(() => new Date(), []); - const { columns: logColumns } = useLogAccessInfo(); + const { logsColumns: logColumns } = useLogAccessInfo(); // Grid --- const columns = React.useMemo((): FluentColumns => { diff --git a/esp/src/src-react/components/Menu.tsx b/esp/src/src-react/components/Menu.tsx index d2c1e3da292..f9bc7b5a5e3 100644 --- a/esp/src/src-react/components/Menu.tsx +++ b/esp/src/src-react/components/Menu.tsx @@ -2,11 +2,11 @@ import * as React from "react"; import { IconButton, IContextualMenuItem, INavLink, INavLinkGroup, Link, mergeStyleSets, Nav, Stack } from "@fluentui/react"; import { useConst } from "@fluentui/react-hooks"; import nlsHPCC from "src/nlsHPCC"; -import { hasLogAccess } from "src/ESPLog"; import { containerized, bare_metal } from "src/BuildInfo"; import { navCategory } from "../util/history"; import { MainNav, routes } from "../routes"; import { useFavorite, useFavorites, useHistory } from "../hooks/favorite"; +import { useLogAccessInfo } from "../hooks/platform"; import { useSessionStore } from "../hooks/store"; import { useUserTheme } from "../hooks/theme"; import { useMyAccount } from "../hooks/user"; @@ -295,12 +295,7 @@ export const SubNavigation: React.FunctionComponent = ({ } }), [theme]); - const [logsDisabled, setLogsDisabled] = React.useState(true); - React.useEffect(() => { - hasLogAccess().then(response => { - setLogsDisabled(!response); - }); - }, []); + const { logsEnabled, logsStatusMessage } = useLogAccessInfo(); const linkStyle = React.useCallback((disabled) => { return disabled ? { background: themeV9.colorNeutralBackgroundDisabled, @@ -326,9 +321,10 @@ export const SubNavigation: React.FunctionComponent = ({ {subMenuItems[mainNav]?.map((row, idx) => { - const linkDisabled = (row.itemKey === "/topology/logs" && logsDisabled) || (row.itemKey.indexOf("security") > -1 && !isAdmin); + const linkDisabled = (row.itemKey === "/topology/logs" && !logsEnabled) || (row.itemKey.indexOf("security") > -1 && !isAdmin); return = ({ const [variables, , , refreshVariables] = useWorkunitVariables(wuid); const [otTraceParent, setOtTraceParent] = React.useState(""); const [logCount, setLogCount] = React.useState("*"); - const [logsDisabled, setLogsDisabled] = React.useState(true); + const { logsEnabled, logsStatusMessage } = useLogAccessInfo(); const [_nextPrev, setNextPrev] = useNextPrev(); const query = React.useMemo(() => { @@ -121,13 +120,6 @@ export const WorkunitDetails: React.FunctionComponent = ({ }; }, [nextWuid, query, setNextPrev, wuid]); - useDeepEffect(() => { - hasLogAccess().then(response => { - setLogsDisabled(!response); - return response; - }); - }, [wuid], [queryParams]); - const onTabSelect = React.useCallback((tab: TabInfo) => { pushUrl(tab.__state ?? `${parentUrl}/${wuid}/${tab.id}`); updateFullscreen(fullscreen); @@ -174,7 +166,8 @@ export const WorkunitDetails: React.FunctionComponent = ({ id: "logs", label: nlsHPCC.Logs, count: logCount, - disabled: logsDisabled + tooltipText: !logsEnabled ? (logsStatusMessage || nlsHPCC.LogsDisabled) : null, + disabled: !logsEnabled }, { id: "eclsummary", label: nlsHPCC.ECL @@ -182,7 +175,7 @@ export const WorkunitDetails: React.FunctionComponent = ({ id: "xml", label: nlsHPCC.XML }]; - }, [logCount, logsDisabled, workunit?.ApplicationValueCount, workunit?.DebugValueCount, workunit?.GraphCount, workunit?.HelpersCount, workunit?.ResourceURLCount, workunit?.ResultCount, workunit?.SourceFileCount, workunit?.VariableCount, workunit?.WorkflowCount, wuid]); + }, [logCount, logsEnabled, logsStatusMessage, workunit?.ApplicationValueCount, workunit?.DebugValueCount, workunit?.GraphCount, workunit?.HelpersCount, workunit?.ResourceURLCount, workunit?.ResultCount, workunit?.SourceFileCount, workunit?.VariableCount, workunit?.WorkflowCount, wuid]); return {({ size }) => diff --git a/esp/src/src-react/components/controls/TabbedPanes/OverflowTabList.tsx b/esp/src/src-react/components/controls/TabbedPanes/OverflowTabList.tsx index e9cf3520ccf..63a15308e08 100644 --- a/esp/src/src-react/components/controls/TabbedPanes/OverflowTabList.tsx +++ b/esp/src/src-react/components/controls/TabbedPanes/OverflowTabList.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Overflow, OverflowItem, SelectTabData, SelectTabEvent, Tab, TabList } from "@fluentui/react-components"; +import { Overflow, OverflowItem, SelectTabData, SelectTabEvent, Tab, TabList, Tooltip } from "@fluentui/react-components"; import { Count } from "./Count"; import { TabInfo } from "./TabInfo"; import { OverflowMenu } from "../OverflowMenu"; @@ -28,7 +28,9 @@ export const OverflowTabList: React.FunctionComponent = ({ tab.__state = state; } return - {tab.label} + + {tab.label} + ; }), tabsIndex]; }, [selected, state, tabs]); diff --git a/esp/src/src-react/components/controls/TabbedPanes/TabInfo.ts b/esp/src/src-react/components/controls/TabbedPanes/TabInfo.ts index 7d87929b3a6..d21edad8120 100644 --- a/esp/src/src-react/components/controls/TabbedPanes/TabInfo.ts +++ b/esp/src/src-react/components/controls/TabbedPanes/TabInfo.ts @@ -6,5 +6,6 @@ export interface TabInfo { label: string; count?: string | number; disabled?: boolean; + tooltipText?: string; __state?: any; } diff --git a/esp/src/src-react/hooks/platform.ts b/esp/src/src-react/hooks/platform.ts index 33f2089c260..e403a37c119 100644 --- a/esp/src/src-react/hooks/platform.ts +++ b/esp/src/src-react/hooks/platform.ts @@ -3,6 +3,7 @@ import { Octokit } from "octokit"; import { useConst } from "@fluentui/react-hooks"; import { scopedLogger } from "@hpcc-js/util"; import { LogaccessService, Topology, WsLogaccess, WsTopology, WorkunitsServiceEx } from "@hpcc-js/comms"; +import nlsHPCC from "src/nlsHPCC"; import { getBuildInfo, BuildInfo, fetchModernMode } from "src/Session"; import { cmake_build_type, containerized, ModernMode } from "src/BuildInfo"; import { sessionKeyValStore, userKeyValStore } from "src/KeyValStore"; @@ -209,19 +210,34 @@ export function useModernMode(): { return { modernMode, setModernMode }; } -export function useLogAccessInfo(): { - managerType: string; - columns: WsLogaccess.Column[] -} { - const [managerType, setManagerType] = React.useState(""); - const [columns, setColumns] = React.useState(); +interface LogAccessInfo { + logsEnabled: boolean; + logsManagerType: string; + logsColumns: WsLogaccess.Column[]; + logsStatusMessage: string; +} + +export function useLogAccessInfo(): LogAccessInfo { + const [logsEnabled, setLogsEnabled] = React.useState(false); + const [logsManagerType, setLogsManagerType] = React.useState(""); + const [logsColumns, setLogsColumns] = React.useState(); + const [logsStatusMessage, setLogsStatusMessage] = React.useState(""); React.useEffect(() => { service.GetLogAccessInfo({}).then(response => { - setManagerType(response.RemoteLogManagerType ?? ""); - setColumns(response?.Columns?.Column); + if (response.hasOwnProperty("Exceptions")) { + setLogsStatusMessage(response["Exceptions"]?.Exception[0]?.Message ?? nlsHPCC.LogAccess_GenericException); + } else { + if (response.RemoteLogManagerType === null) { + setLogsStatusMessage(nlsHPCC.LogAccess_LoggingNotConfigured); + } else { + setLogsEnabled(true); + setLogsManagerType(response.RemoteLogManagerType); + setLogsColumns(response?.Columns?.Column); + } + } }); }, []); - return { managerType, columns }; + return { logsEnabled, logsManagerType, logsColumns, logsStatusMessage }; } \ No newline at end of file diff --git a/esp/src/src/nls/hpcc.ts b/esp/src/src/nls/hpcc.ts index 592e8cb09d3..182faf5b59f 100644 --- a/esp/src/src/nls/hpcc.ts +++ b/esp/src/src/nls/hpcc.ts @@ -496,6 +496,8 @@ export = { Location: "Location", Lock: "Lock", LogAccessType: "Log Access Type", + LogAccess_GenericException: "ws_logaccess::GetLogAccessInfo, an exception has occurred.", + LogAccess_LoggingNotConfigured: "A logging engine has not been configured.", LogDirectory: "Log Directory", LogEventType: "Log Event Type", LogFile: "Log File", @@ -529,6 +531,7 @@ export = { Login: "Login", Logout: "Log Out", Logs: "Logs", + LogsDisabled: "Logs Disabled", LogVisualization: "Log Visualization", LogVisualizationUnconfigured: "Log Visualization is not configured, please check your configuration manager settings.", log_analysis_1: "log_analysis_1*",