adding "host" option for mythic networking and test workflow files

This commit is contained in:
its-a-feature
2024-08-27 17:28:15 -05:00
parent f82a81dc9a
commit 65e32fc687
35 changed files with 895 additions and 438 deletions
+6
View File
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.3.0-rc23] - 2024-08-27
### Changed
- Added additional checks for eventing
## [3.3.0-rc22]
### Changed
+7
View File
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.2.34] - 2024-08-27
### Changed
- Updated the Eventing table to use the same table format as the callbacks table
- Added a test button for the eventing to check for some mistakes before uploading
## [0.2.33] - 2024-08-26
### Changed
@@ -75,6 +75,7 @@ const ResizableGridWrapper = ({
rowContextMenuOptions,
onRowContextMenuClick,
rowHeight,
headerRowHeight,
widthMeasureKey,
callbackTableGridRef,
onRowClick,
@@ -95,9 +96,12 @@ const ResizableGridWrapper = ({
);
const getRowHeight = useCallback(
(index) => {
if(index === 0){
return headerRowHeight;
}
return rowHeight;
},
[rowHeight]
[rowHeight, headerRowHeight]
);
useEffect(() => {
@@ -259,7 +263,7 @@ const ResizableGridWrapper = ({
</VariableSizeGrid>
<DraggableHandles
height={AutoSizerProps.height}
rowHeight={getRowHeight(0)}
rowHeight={headerRowHeight}
width={AutoSizerProps.width}
minColumnWidth={MIN_COLUMN_WIDTH}
columnWidths={columnWidths}
@@ -285,6 +289,7 @@ const MythicResizableGrid = ({
onRowContextMenuClick,
widthMeasureKey,
rowHeight = 20,
headerRowHeight = 20,
callbackTableGridRef,
onRowClick,
}) => {
@@ -300,6 +305,7 @@ const MythicResizableGrid = ({
items={items}
widthMeasureKey={widthMeasureKey}
rowHeight={rowHeight}
headerRowHeight={headerRowHeight}
onClickHeader={onClickHeader}
onDoubleClickRow={onDoubleClickRow}
contextMenuOptions={contextMenuOptions}
@@ -330,6 +336,7 @@ MythicResizableGrid.propTypes = {
contextMenuOptions: PropTypes.array,
rowContextMenuOptions: PropTypes.array,
rowHeight: PropTypes.number,
headerRowHeight: PropTypes.number,
headerNameKey: PropTypes.string,
widthMeasureKey: PropTypes.string
};
@@ -321,9 +321,9 @@ const DisplayFileMetaData = ({fileMetaData}) => {
<TableBody>
<TableRow>
<MythicStyledTableCell><TableRowSizeCell cellData={fileMetaData.size}/></MythicStyledTableCell>
<MythicStyledTableCell>{fileMetaData.host}</MythicStyledTableCell>
<MythicStyledTableCell>{fileMetaData.filename}</MythicStyledTableCell>
<MythicStyledTableCell>{fileMetaData.full_remote_path}</MythicStyledTableCell>
<MythicStyledTableCell style={{wordBreak: "break-all"}}>{fileMetaData.host}</MythicStyledTableCell>
<MythicStyledTableCell style={{wordBreak: "break-all"}}>{fileMetaData.filename}</MythicStyledTableCell>
<MythicStyledTableCell style={{wordBreak: "break-all"}}>{fileMetaData.full_remote_path}</MythicStyledTableCell>
<MythicStyledTableCell><Link style={{wordBreak: "break-all"}}
color="textPrimary" underline="always"
target="_blank" href={"/new/task/" + fileMetaData.task_display_id}>
@@ -33,9 +33,15 @@ export const ResponseDisplayPlaintext = (props) =>{
const me = useReactiveVar(meState);
const currentContentRef = React.useRef();
const [plaintextView, setPlaintextView] = React.useState("");
const [mode, setMode] = React.useState("html");
const initialMode = props?.initial_mode || "html";
const [mode, setMode] = React.useState(initialMode);
const [wrapText, setWrapText] = React.useState(true);
const [showOptions, setShowOptions] = React.useState(false);
const onChangeText = (data) => {
if(props.onChangeContent){
props?.onChangeContent(data);
}
}
useEffect( () => {
if(props.plaintext.length > MaxRenderSize){
snackActions.warning("Response too large (> 2MB), truncating the render. Download task output to view entire response.");
@@ -70,7 +76,7 @@ export const ResponseDisplayPlaintext = (props) =>{
}
const scrollContent = (node, isAppearing) => {
// only auto-scroll if you issued the task
if(props.task.operator.username === (me?.user?.username || "")){
if(props?.task?.operator?.username === (me?.user?.username || "")){
let el = document.getElementById(`taskingPanel${props.task.callback_id}`);
if(props.expand || props.displayType === "console"){
el = document.getElementById(`taskingPanelConsole${props.task.callback_id}`);
@@ -140,6 +146,7 @@ export const ResponseDisplayPlaintext = (props) =>{
theme={theme.palette.mode === "dark" ? "monokai" : "xcode"}
fontSize={14}
showGutter={true}
onChange={onChangeText}
//onLoad={onLoad}
highlightActiveLine={false}
showPrintMargin={false}
@@ -98,8 +98,9 @@ export function EventGroupInstances({selectedEventGroup, me, setSelectedInstance
});
return (
<EventGroupInstancesTableMaterialReactTable setSelectedInstance={setSelectedInstance}
selectedInstanceID={selectedInstanceID}
eventgroups={selectedEventGroups} me={me} />
<EventGroupInstancesTableMaterialReactTable
setSelectedInstance={setSelectedInstance}
selectedInstanceID={selectedInstanceID}
eventgroups={selectedEventGroups} me={me} />
)
}
@@ -18,11 +18,10 @@ import OpenInNewTwoToneIcon from '@mui/icons-material/OpenInNewTwoTone';
import Typography from '@mui/material/Typography';
import IosShareIcon from '@mui/icons-material/IosShare';
import {copyStringToClipboard} from "../../utilities/Clipboard";
import {useTheme} from '@mui/material/styles';
import {
MaterialReactTable,
useMaterialReactTable,
} from 'material-react-table';
import {ipCompare} from "../Callbacks/CallbacksTable";
import MythicResizableGrid from "../../MythicComponents/MythicResizableGrid";
import {TableFilterDialog} from "../Callbacks/TableFilterDialog";
import {CallbacksTableStringCell} from "../Callbacks/CallbacksTableRow";
const cancelEventGroupInstanceMutation = gql(`
mutation cancelEventGroupInstanceMutation($eventgroupinstance_id: Int!){
@@ -78,26 +77,10 @@ export const adjustOutput = (e, newTime) => {
}
return newTime;
}
const accessorFn = (row, h) => {
if(h.type === "timestamp"){
let d = new Date(row[h.key] || 0);
if (d.getFullYear() === 1970){
d = new Date();
d = d + d.getTimezoneOffset();
}
return d;
}
if(h.type === "number" || h.type === "size"){
try{
return Number(row[h.key] || 0);
}catch(error){
return row[h.key] || 0;
}
}
return row[h.key] || "";
};
function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, setSelectedInstance, selectedInstanceID}){
const theme = useTheme();
const callbackTableGridRef = React.useRef();
const [openDeleteDialog, setOpenDeleteDialog] = React.useState(false);
const [openRetryDialog, setOpenRetryDialog] = React.useState(false);
const [openRunAgainDialog, setOpenRunAgainDialog] = React.useState(false);
@@ -173,6 +156,8 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
copyStringToClipboard(path);
snackActions.success("copied shareable link to clipboard");
}
const [sortData, setSortData] = React.useState({"sortKey": null, "sortDirection": null, "sortType": null});
const [filterOptions, setFilterOptions] = React.useState({});
React.useEffect( () => {
if( !foundQueryEvent.current ){
let queryParams = new URLSearchParams(window.location.search);
@@ -187,220 +172,239 @@ function EventGroupInstancesTableMaterialReactTablePreMemo({eventgroups, me, set
}
}, [eventgroups]);
const columnFields = [
{key: "id", type: "number", name: "ID", width: 40, disableCopy: true, enableHiding: false, disableSort: true, disableFilter: true},
{key: "status", type: 'string', name: "Status", width: 100, disableCopy: true, disableSort: true, enableHiding: false},
{key: "event_group", type: 'string', name: "Event Group", fillWidth: true, disableCopy: true, enableHiding: false},
{key: "trigger", type: 'string', name: "Trigger", fillWidth: true, enableHiding: false},
{key: "time", type: 'date', name: "Time", width: 180, enableHiding: false},
{key: "operator", type: 'string', name: "Operator", fillWidth: true, enableHiding: false},
{key: "cancel", type: 'string', name: "Action", width: 50, disableCopy: true, enableHiding: false, disableSort: true, disableFilter: true},
const columns = [
{key: "id", type: "number", name: "ID", width: 60, enableHiding: false, disableSort: true, disableFilter: true},
{key: "status", type: 'string', name: "Status", width: 150, disableSort: true, enableHiding: false},
{key: "event_group", type: 'string', name: "Event Group", inMetadata: true, fillWidth: true, disableSort: true, enableHiding: false},
{key: "trigger", type: 'string', name: "Trigger", fillWidth: true, disableSort: true, enableHiding: false},
{key: "time", type: 'date', name: "Time", width: 300, disableSort: true,},
{key: "operator", type: 'string', name: "Operator", inMetadata: true, fillWidth: true, disableSort: true,},
{key: "cancel", type: 'string', name: "Action", width: 70, disableSort: true, disableFilter: true},
];
const localCellRender = React.useCallback( ({cell, h}) => {
let row = cell.row?.original;
switch(h.name){
case "ID":
return (
row.id
)
case "Status":
return (
<>
<GetStatusSymbol data={row} />
{
selectedInstanceID === 0 ?
(
<IconButton onClick={() => {setSelectedInstance(row.id);}} >
<CastConnectedTwoToneIcon />
</IconButton>
) :
(
<IconButton onClick={() => {setSelectedInstance(0);}} >
<CancelTwoToneIcon />
</IconButton>
)
}
<IconButton onClick={() => {openViewInstanceLargeDialog(row)}}>
<OpenInNewTwoToneIcon />
</IconButton>
<IconButton onClick={() => onSaveToClipboard(row)}>
<IosShareIcon />
</IconButton>
</>
)
case "Event Group":
return (
<Typography >
{row.eventgroup.name}
</Typography>
)
case "Trigger":
return (
row.trigger
)
case "Time":
return (
<div>
<div style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
<CalendarMonthTwoToneIcon style={{marginRight: "10px"}}/>
{toLocalTime(row?.created_at, me?.user?.view_utc_time)}
</div>
<div style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
<AccessAlarmTwoToneIcon style={{marginRight: "10px"}}/>
{row.end_timestamp === null &&
<Moment filter={(newTime) => adjustOutput(row, newTime)} interval={1000}
parse={"YYYY-MM-DDTHH:mm:ss.SSSSSSZ"}
withTitle
titleFormat={"YYYY-MM-DD HH:mm:ss"}
fromNow ago
>
{row.created_at + "Z"}
</Moment>
}
{row.end_timestamp !== null &&
<Moment filter={(newTime) => adjustDurationOutput(row, newTime)}
parse={"YYYY-MM-DDTHH:mm:ss.SSSSSSZ"}
withTitle
titleFormat={"YYYY-MM-DD HH:mm:ss"}
>
{row.created_at + "Z"}
</Moment>
}
</div>
</div>
)
case "Operator":
return (
row?.operator?.username
)
case "Action":
return (
row.end_timestamp === null ? (
<MythicStyledTooltip title={"Cancel Eventing"} >
<IconButton onClick={() => {onOpenCancelDialog({id: row.id});}} color={"warning"}>
<CancelTwoToneIcon />
</IconButton>
</MythicStyledTooltip>
) : row.status === "error" || row.status === "cancelled" ? (
<MythicStyledTooltip title={"Retry Failed / Canceled Steps"} >
<IconButton onClick={() => {onOpenRetryDialog({id: row.id});}} color={"warning"}>
<ReplayIcon />
</IconButton>
</MythicStyledTooltip>
) : row.status === "success" ? (
<MythicStyledTooltip title={"Run Again"} >
<IconButton onClick={() => {onOpenRunAgainDialog({id: row.id});}} color={"success"}>
<ReplayIcon />
</IconButton>
</MythicStyledTooltip>
) : null
)
const onClickHeader = (e, columnIndex) => {
const column = columns[columnIndex];
if(column.disableSort){
return;
}
}, [selectedInstanceID]);
const columns = React.useMemo(() => columnFields.map(h => {
return {
accessorKey: h.key,
header: h.name,
size: h.width,
id: h.key,
enableClickToCopy: !h.disableCopy,
filterVariant: h.type === 'number' || h.type === 'size' ? 'range' : 'text',
enableResizing: true,
enableHiding: h.enableHiding,
enableSorting: !h.disableSort,
enableColumnFilter: !h.disableFilter,
grow: h.fillWidth,
accessorFn: (row) => accessorFn(row, h),
Cell: ({cell}) => localCellRender({cell, h})
if (!column.key) {
setSortData({"sortKey": null, "sortType":null, "sortDirection": "ASC"});
}
}), [columnFields])
const materialReactTable = useMaterialReactTable({
columns,
data: eventgroups,
layoutMode: "grid",
autoResetPageIndex: false,
enableFacetedValues: true,
enablePagination: true,
//enableRowVirtualization: true,
enableBottomToolbar: false,
enableStickyHeader: true,
enableDensityToggle: false,
enableColumnResizing: true,
enableRowPinning: false,
positionPagination: "top",
columnFilterDisplayMode: 'popover', //filter inputs will show in a popover (like excel)
rowPinningDisplayMode: 'top-and-bottom',
//enableColumnOrdering: true,
//columnResizeMode: 'onEnd',
initialState: {
density: 'compact',
},
defaultDisplayColumn: { enableResizing: true },
muiTableContainerProps: { sx: { alignItems: "flex-start" } },
mrtTheme: (theme) => ({
baseBackgroundColor: theme.palette.background.default, //change default background color
}),
muiSearchTextFieldProps: {
placeholder: 'Search loaded data',
size: 'small',
sx: { minWidth: '300px' },
variant: 'outlined',
},
muiTableHeadCellProps: {
sx: {
border: '1px solid rgba(81, 81, 81, .5)',
fontStyle: 'italic',
fontWeight: 'bold',
},
style: {
zIndex: 1,
height: "36px",
if (sortData.sortKey === column.key) {
if (sortData.sortDirection === 'ASC') {
setSortData({...sortData, "sortDirection": "DESC"});
} else {
setSortData({"sortKey": null, "sortType":null, "sortDirection": "ASC"});
}
},
muiTableHeadRowProps: {
sx: {
alignItems: "flex-start",
height: "36px",
}
},
muiTableBodyCellProps: ({ cell, table }) => {
return {
sx: {
padding: "0 0 0 10px",
} else {
setSortData({"sortKey": column.key, "sortType":column.type, "sortDirection": "ASC"});
}
};
const filterRow = (row) => {
for(const [key,value] of Object.entries(filterOptions)){
if(key === "operator"){
if(!String(row?.operator?.username).toLowerCase().includes(String(value).toLowerCase())){
return true;
}
}else {
if(!String(row[key]).toLowerCase().includes(String(value).toLowerCase())){
return true;
}
}
},
muiTableBodyRowProps: ({ row }) => ({
sx: {
height: "40px",
},
style: {padding: 0}
}),
enableRowActions: false,
muiTablePaperProps: {
sx: { display: "flex", flexDirection: "column", width: "100%"}
},
muiTopToolbarProps: {
sx: {
backgroundColor: theme.materialReactTableHeader,
display: "flex",
justifyContent: "flex-start"
}
},
renderEmptyRowsFallback: ({ table }) => (
<div style={{display: "flex", width: "100%", height: "100%", justifyContent: "center", flexDirection: "column", alignItems: "center"}}>
<Typography variant={"h4"} >
{eventgroups.length === 0 ? "No Workflows" : null}
</Typography>
</div>
),
});
}
return false;
}
const sortedData = React.useMemo(() => {
const tempData = [...eventgroups];
if (sortData.sortType === 'number' || sortData.sortType === 'size' || sortData.sortType === 'date') {
tempData.sort((a, b) => (parseInt(a[sortData.sortKey]) > parseInt(b[sortData.sortKey]) ? 1 : -1));
} else if (sortData.sortType === 'string') {
tempData.sort((a, b) => (a[sortData.sortKey].toLowerCase() > b[sortData.sortKey].toLowerCase() ? 1 : -1));
} else if (sortData.sortType === "ip") {
tempData.sort((a, b) => (ipCompare(a[sortData.sortKey], b[sortData.sortKey])));
} else if (sortData.sortType === "array") {
tempData.sort((a, b) => (
a[sortData.sortKey] > b[sortData.sortKey] ? 1 : -1
))
} else if (sortData.sortType === "timestamp") {
tempData.sort((a, b) => {
let aDate = new Date(a[sortData.sortKey]);
let bDate = new Date(b[sortData.sortKey]);
if (aDate.getFullYear() === 1970) {
if (bDate.getFullYear() === 1970) {
return 0;
}
return 1;
} else if (bDate.getFullYear() === 1970) {
return -1;
}
if (aDate > bDate) {
return 1
} else if (bDate > aDate) {
return -1
}
return 0;
})
} else if (sortData.sortType === "agent") {
tempData.sort((a, b) => (a?.payload?.payloadtype?.name?.toLowerCase() > b?.payload?.payloadtype?.name?.toLowerCase() ? 1 : -1));
}
if (sortData.sortDirection === 'DESC') {
tempData.reverse();
}
return tempData.reduce( (prev, row) => {
if(filterRow(row)){
return [...prev];
}
return [...prev, columns.map( c => {
switch(c.name){
case "ID":
return <CallbacksTableStringCell rowData={row} cellData={row.id} />
case "Status":
return (
<div style={{display: "flex", flexDirection:"row", alignItems: "center"}}>
<GetStatusSymbol data={row} />
{
selectedInstanceID === 0 ?
(
<IconButton onClick={() => {setSelectedInstance(row.id);}} >
<CastConnectedTwoToneIcon />
</IconButton>
) :
(
<IconButton onClick={() => {setSelectedInstance(0);}} >
<CancelTwoToneIcon />
</IconButton>
)
}
<IconButton onClick={() => {openViewInstanceLargeDialog(row)}}>
<OpenInNewTwoToneIcon />
</IconButton>
<IconButton onClick={() => onSaveToClipboard(row)}>
<IosShareIcon />
</IconButton>
</div>
)
case "Event Group":
return <CallbacksTableStringCell rowData={row} cellData={row.eventgroup.name} />
case "Trigger":
return <CallbacksTableStringCell rowData={row} cellData={row.trigger} />
case "Time":
return (
<div>
<div style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
<CalendarMonthTwoToneIcon style={{marginRight: "10px"}}/>
{toLocalTime(row?.created_at, me?.user?.view_utc_time)}
</div>
<div style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
<AccessAlarmTwoToneIcon style={{marginRight: "10px"}}/>
{row.end_timestamp === null &&
<Moment filter={(newTime) => adjustOutput(row, newTime)} interval={1000}
parse={"YYYY-MM-DDTHH:mm:ss.SSSSSSZ"}
withTitle
titleFormat={"YYYY-MM-DD HH:mm:ss"}
fromNow ago
>
{row.created_at + "Z"}
</Moment>
}
{row.end_timestamp !== null &&
<Moment filter={(newTime) => adjustDurationOutput(row, newTime)}
parse={"YYYY-MM-DDTHH:mm:ss.SSSSSSZ"}
withTitle
titleFormat={"YYYY-MM-DD HH:mm:ss"}
>
{row.created_at + "Z"}
</Moment>
}
</div>
</div>
)
case "Operator":
return <CallbacksTableStringCell rowData={row} cellData={row?.operator?.username} />
case "Action":
return (
<div style={{display: "flex", alignItems: "center"}}>
{row.end_timestamp === null ? (
<MythicStyledTooltip title={"Cancel Eventing"} >
<IconButton onClick={() => {onOpenCancelDialog({id: row.id});}}
color={"warning"}>
<CancelTwoToneIcon />
</IconButton>
</MythicStyledTooltip>
) : row.status === "error" || row.status === "cancelled" ? (
<MythicStyledTooltip title={"Retry Failed / Canceled Steps"} >
<IconButton onClick={() => {onOpenRetryDialog({id: row.id});}}
color={"warning"}>
<ReplayIcon />
</IconButton>
</MythicStyledTooltip>
) : row.status === "success" ? (
<MythicStyledTooltip title={"Run Again"} >
<IconButton onClick={() => {onOpenRunAgainDialog({id: row.id});}}
color={"success"}>
<ReplayIcon />
</IconButton>
</MythicStyledTooltip>
) : null}
</div>
)
}
})]
}, []);
}, [sortData, filterOptions, selectedInstanceID, eventgroups]);
const sortColumn = columns.findIndex((column) => column.key === sortData.sortKey);
const [openContextMenu, setOpenContextMenu] = React.useState(false);
const [selectedColumn, setSelectedColumn] = React.useState({});
const onSubmitFilterOptions = (newFilterOptions) => {
setFilterOptions(newFilterOptions);
}
const contextMenuOptions = [{
name: 'Filter Column',
click: ({event, columnIndex}) => {
event.preventDefault();
event.stopPropagation();
if(columns[columnIndex].disableFilterMenu){
snackActions.warning("Can't filter that column");
return;
}
setSelectedColumn(columns[columnIndex]);
setOpenContextMenu(true);
}
},];
const onRowDoubleClick = () => {
}
const onRowContextClick = ({rowDataStatic}) => {
return [];
}
return (
<div style={{ width: '100%', overflow: "auto", flexGrow: 1, display: "flex"}}>
<MaterialReactTable table={materialReactTable} />
<div style={{ width: '100%', overflow: "auto", flexGrow: 1, display: "flex", position: "relative"}}>
<MythicResizableGrid
callbackTableGridRef={callbackTableGridRef}
columns={columns}
sortIndicatorIndex={sortColumn}
sortDirection={sortData.sortDirection}
items={sortedData}
rowHeight={40}
headerRowHeight={20}
onClickHeader={onClickHeader}
onDoubleClickRow={onRowDoubleClick}
contextMenuOptions={contextMenuOptions}
onRowContextMenuClick={onRowContextClick}
/>
{openContextMenu &&
<MythicDialog fullWidth={true} maxWidth="xs" open={openContextMenu}
onClose={()=>{setOpenContextMenu(false);}}
innerDialog={<TableFilterDialog
selectedColumn={selectedColumn}
filterOptions={filterOptions}
onSubmit={onSubmitFilterOptions}
onClose={()=>{setOpenContextMenu(false);}} />}
/>
}
{openDeleteDialog &&
<MythicConfirmDialog onClose={() => {
setOpenDeleteDialog(false);
@@ -16,6 +16,9 @@ import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
import { IconButton } from '@mui/material';
import NotificationsOffTwoToneIcon from '@mui/icons-material/NotificationsOffTwoTone';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import FactCheckIcon from '@mui/icons-material/FactCheck';
import {MythicDialog} from "../../MythicComponents/MythicDialog";
import {TestEventGroupFileDialog} from "../Search/PreviewFileMedia";
const get_eventgroups = gql`
query GetEventGroups {
@@ -126,6 +129,7 @@ subscription GetEventGroups {
export function Eventing({me}){
const theme = useTheme();
const [openTestModal, setOpenTestModal] = React.useState(false);
const [eventgroups, setEventgroups] = React.useState([]);
const [showDeleted, setShowDeleted] = React.useState(false);
const [selectedEventGroup, setSelectedEventGroup] = React.useState({id: 0});
@@ -212,13 +216,33 @@ export function Eventing({me}){
<div style={{display: "flex", flexDirection: "column", height: "100%", overflowY: "auto"}}>
<div style={{display: "flex", flexDirection: "row", height: "100%"}}>
<div style={{width: "20%", borderRight: "2px solid grey", height: '100%', display: "flex", flexDirection: "column"}}>
<Button size={"small"} style={{float: "right", marginRight: "10px"}}
variant={"contained"} color={"success"} component="label"
startIcon={<CloudUploadIcon />}
>
New Event Groups
<input onChange={onFileChange} type="file" multiple hidden/>
</Button>
<div style={{marginBottom: "5px"}}>
<Button size={"small"} style={{display: "inline-flex", marginRight: "10px", marginLeft: "10px", marginTop: "5px"}}
variant={"contained"} color={"success"} component="label"
startIcon={<CloudUploadIcon />}
>
Upload New
<input onChange={onFileChange} type="file" multiple hidden/>
</Button>
<Button size={"small"} variant={"contained"} color={"info"}
startIcon={<FactCheckIcon />}
style={{display: "inline-flex", marginRight: "10px", marginLeft: "10px", marginTop: "5px"}}
onClick={()=>setOpenTestModal(true)}>
Create & Test
</Button>
</div>
{openTestModal &&
<MythicDialog fullWidth={true} maxWidth="xl" open={openTestModal}
onClose={(e) => {
setOpenTestModal(false);
}}
innerDialog={<TestEventGroupFileDialog
onClose={(e) => {
setOpenTestModal(false);
}}/>}
/>
}
<ListItem button onClick={() => setSelectedEventGroup({id: 0})}
style={selectedEventGroup.id === 0 ?
{paddingTop: 0, paddingBottom: 0, borderLeft: `5px solid ${theme.palette.info.main}`} :
@@ -7,6 +7,10 @@ import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/theme-monokai';
import 'ace-builds/src-noconflict/theme-xcode';
import {ResponseDisplayMedia} from "../Callbacks/ResponseDisplayMedia";
import {ResponseDisplayPlaintext} from "../Callbacks/ResponseDisplayPlaintext";
import {UploadEventFile} from "../../MythicComponents/MythicFileUpload";
import {snackActions} from "../../utilities/Snackbar";
import { gql, useLazyQuery } from '@apollo/client';
export function PreviewFileMediaDialog({agent_file_id, filename, onClose}) {
return (
@@ -25,4 +29,62 @@ export function PreviewFileMediaDialog({agent_file_id, filename, onClose}) {
</React.Fragment>
);
}
const testFileWebhookMutation = gql`
query testEventGroupFile($file_contents: String!){
eventingTestFile(file_contents: $file_contents){
status
error
}
}
`;
export function TestEventGroupFileDialog({onClose}){
const [fileText, setFileText] = React.useState("");
const submitAsFile = async (evt) => {
let blob = new Blob([fileText], { type: 'text/plain' });
let file = new File([blob], "manual_eventing.yaml", {type: "text/plain"});
let uploadStatus = await UploadEventFile(file, "New Manual Eventing Workflow");
if(!uploadStatus){
snackActions.error("Failed to upload file");
}
if(uploadStatus.status === "error"){
snackActions.error(uploadStatus.error);
}
onClose();
}
const [testFileMutation] = useLazyQuery(testFileWebhookMutation, {
onCompleted: (data) => {
if(data.eventingTestFile.status === "success"){
snackActions.success("No error detected");
} else {
snackActions.error(data.eventingTestFile.error);
}
},
onError: (data) => {
console.log(data);
}
})
const testFile = () => {
testFileMutation({variables: {file_contents: fileText}});
}
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">
Create and Verify Eventing Workflow
</DialogTitle>
<DialogContent style={{height: "calc(95vh)", margin: 0, padding: 0}}>
<ResponseDisplayPlaintext plaintext={fileText} onChangeContent={setFileText} initial_mode={"yaml"} expand={true} />
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={onClose} color="primary">
Close
</Button>
<Button variant={"contained"} color={"success"} onClick={testFile}>
Test
</Button>
<Button variant={"contained"} color={"warning"} onClick={submitAsFile}>
Save and Submit
</Button>
</DialogActions>
</React.Fragment>
)
}
+1 -1
View File
@@ -14,7 +14,7 @@ import {snackActions} from './components/utilities/Snackbar';
import jwt_decode from 'jwt-decode';
import {meState} from './cache';
export const mythicUIVersion = "0.2.33";
export const mythicUIVersion = "0.2.34";
let fetchingNewToken = false;
+7
View File
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## 0.3.4 - 2024-08-27
### Changed
- Added option for `mythic_docker_networking` to allow for `bridge` or `host` networking
- This applies to all main mythic services
## 0.3.3 - 2024-08-11
### Changed
+113 -43
View File
@@ -51,7 +51,7 @@ func GetIntendedMythicServiceNames() ([]string, error) {
containerList = append(containerList, service)
}
case "mythic_server":
if mythicEnv.GetString("MYTHIC_SERVER_DOCKER_NETWORKING") == "host" {
if mythicEnv.GetString("MYTHIC_DOCKER_NETWORKING") == "host" {
containerList = append(containerList, service)
} else if mythicEnv.GetString("MYTHIC_SERVER_HOST") == "127.0.0.1" || mythicEnv.GetString("MYTHIC_SERVER_HOST") == "mythic_server" {
containerList = append(containerList, service)
@@ -125,7 +125,9 @@ func setMythicConfigDefaultValues() {
mythicEnvInfo["nginx_port"] = `This sets the port used for the Nginx reverse proxy - this port is used by the React UI and Mythic's Scripting`
mythicEnv.SetDefault("nginx_host", "mythic_nginx")
mythicEnvInfo["nginx_host"] = `This specifies the ip/hostname for where the Nginx container executes. If this is "mythic_nginx" or "127.0.0.1", then mythic-cli assumes this container is running locally. If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnvInfo["nginx_host"] = `This specifies the ip/hostname for where the Nginx container executes.
If this is "mythic_nginx" or "127.0.0.1", then mythic-cli assumes this container is running locally.
If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnv.SetDefault("nginx_bind_localhost_only", false)
mythicEnvInfo["nginx_bind_localhost_only"] = `This specifies if the Nginx container will expose the nginx_port on 0.0.0.0 or 127.0.0.1`
@@ -140,58 +142,79 @@ func setMythicConfigDefaultValues() {
mythicEnvInfo["nginx_use_ipv6"] = `This specifies if the Nginx reverse proxy should bind to IPv6 or not`
mythicEnv.SetDefault("nginx_use_volume", false)
mythicEnvInfo["nginx_use_volume"] = `The Nginx container gets dynamic configuration from a variety of .env values as well as dynamically created SSL certificates. If this is True, then a docker volume is created and mounted into the container to host these pieces. If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnvInfo["nginx_use_volume"] = `The Nginx container gets dynamic configuration from a variety of .env values as well as dynamically created SSL certificates.
If this is True, then a docker volume is created and mounted into the container to host these pieces.
If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnv.SetDefault("nginx_use_build_context", false)
mythicEnvInfo["nginx_use_build_context"] = `The Nginx container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io). Setting this to "true" means that the local Mythic/nginx-docker/Dockerfile is used to generate the image used for the mythic_nginx container instead of the hosted image.`
mythicEnvInfo["nginx_use_build_context"] = `The Nginx container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io).
Setting this to "true" means that the local Mythic/nginx-docker/Dockerfile is used to generate the image used for the mythic_nginx container instead of the hosted image.`
// mythic react UI configuration ---------------------------------------------
mythicEnv.SetDefault("mythic_react_host", "mythic_react")
mythicEnvInfo["mythic_react_host"] = `This specifies the ip/hostname for where the React UI container executes. If this is 'mythic_react' or '127.0.0.1', then mythic-cli assumes this container is running locally. If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnvInfo["mythic_react_host"] = `This specifies the ip/hostname for where the React UI container executes.
If this is 'mythic_react' or '127.0.0.1', then mythic-cli assumes this container is running locally.
If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnv.SetDefault("mythic_react_port", 3000)
mythicEnvInfo["mythic_react_port"] = `This specifies the port that the React UI server listens on. This is normally accessed through the nginx reverse proxy though via /new`
mythicEnv.SetDefault("mythic_react_bind_localhost_only", true)
mythicEnvInfo["mythic_react_bind_localhost_only"] = `This specifies if the mythic_react container will expose the mythic_react_port on 0.0.0.0 or 127.0.0.1. Binding the localhost will still allow internal reverse proxying to work, but won't allow the service to be hit remotely. It's unlikely this will ever need to change since you should be connecting through the nginx_proxy, but would be necessary to change if the React UI were hosted on a different server.`
mythicEnvInfo["mythic_react_bind_localhost_only"] = `This specifies if the mythic_react container will expose the mythic_react_port on 0.0.0.0 or 127.0.0.1.
Binding the localhost will still allow internal reverse proxying to work, but won't allow the service to be hit remotely.
It's unlikely this will ever need to change since you should be connecting through the nginx_proxy, but would be necessary to change if the React UI were hosted on a different server.`
mythicEnv.SetDefault("mythic_react_use_volume", false)
mythicEnvInfo["mythic_react_use_volume"] = `This specifies if the mythic_react container will mount mount the local filesystem to serve content or use the pre-build data within the image itself. If you want to change the website that's shown, you need to mount locally and change the mythic_react_use_build_context to true'`
mythicEnvInfo["mythic_react_use_volume"] = `This specifies if the mythic_react container will mount mount the local filesystem to serve content or use the pre-build data within the image itself.
If you want to change the website that's shown, you need to mount locally and change the mythic_react_use_build_context to true'`
mythicEnv.SetDefault("mythic_react_use_build_context", false)
mythicEnvInfo["mythic_react_use_build_context"] = `This specifies if the mythic_react container should use the pre-built docker image hosted on GitHub's container registry (ghcr.io) or if the local mythic-react-docker/Dockerfile should be used to generate the base image for the mythic_react container`
// documentation configuration ---------------------------------------------
mythicEnv.SetDefault("documentation_host", "mythic_documentation")
mythicEnvInfo["documentation_host"] = `This specifies the ip/hostname for where the documentation container executes. If this is 'documentation_host' or '127.0.0.1', then mythic-cli assumes this container is running locally. If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnvInfo["documentation_host"] = `This specifies the ip/hostname for where the documentation container executes.
If this is 'documentation_host' or '127.0.0.1', then mythic-cli assumes this container is running locally. If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnv.SetDefault("documentation_port", 8090)
mythicEnvInfo["documentation_port"] = `This specifies the port that the Documentation UI server listens on. This is normally accessed through the nginx reverse proxy though via /docs`
mythicEnvInfo["documentation_port"] = `This specifies the port that the Documentation UI server listens on.
This is normally accessed through the nginx reverse proxy though via /docs`
mythicEnv.SetDefault("documentation_bind_localhost_only", true)
mythicEnvInfo["documentation_bind_localhost_only"] = `This specifies if the documentation container will expose the documentation_port on 0.0.0.0 or 127.0.0.1`
mythicEnv.SetDefault("documentation_use_volume", false)
mythicEnvInfo["documentation_use_volume"] = `The documentation container gets dynamic from installed agents and c2 profiles. If this is True, then a docker volume is created and mounted into the container to host these pieces. If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnvInfo["documentation_use_volume"] = `The documentation container gets dynamic from installed agents and c2 profiles.
If this is True, then a docker volume is created and mounted into the container to host these pieces.
If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnv.SetDefault("documentation_use_build_context", false)
mythicEnvInfo["documentation_use_build_context"] = `The documentation container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io). Setting this to "true" means that the local Mythic/documentation-docker/Dockerfile is used to generate the image used for the mythic_documentation container instead of the hosted image.`
mythicEnvInfo["documentation_use_build_context"] = `The documentation container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io).
Setting this to "true" means that the local Mythic/documentation-docker/Dockerfile is used to generate the image used for the mythic_documentation container instead of the hosted image.`
// mythic server configuration ---------------------------------------------
mythicEnv.SetDefault("mythic_debug_agent_message", false)
mythicEnvInfo["mythic_debug_agent_message"] = `When this is true, Mythic will send a message to the operational event log for each step of processing every agent's message. This can be a lot of messages, so do it with care, but it can be extremely valuable in figuring out issues with agent messaging. This setting can also be toggled at will in the UI on the settings page by an admin.`
mythicEnvInfo["mythic_debug_agent_message"] = `When this is true, Mythic will send a message to the operational event log for each step of processing every agent's message.
This can be a lot of messages, so do it with care, but it can be extremely valuable in figuring out issues with agent messaging.
This setting can also be toggled at will in the UI on the settings page by an admin.`
mythicEnv.SetDefault("mythic_server_port", 17443)
mythicEnvInfo["mythic_server_port"] = `This specifies the port that the mythic_server listens on. This is normally accessed through the nginx reverse proxy though via /new. Agent and C2 Profile containers will directly access this container and port when fetching/uploading files/payloads.`
mythicEnvInfo["mythic_server_port"] = `This specifies the port that the mythic_server listens on.
This is normally accessed through the nginx reverse proxy though via /new. Agent and C2 Profile containers will directly access this container and port when fetching/uploading files/payloads.`
mythicEnv.SetDefault("mythic_server_grpc_port", 17444)
mythicEnvInfo["mythic_server_grpc_port"] = `This specifies the port that the mythic_server's gRPC functionality listens on. Translation containers will directly access this container and port when establishing gRPC functionality. C2 Profile containers will directly access this container and port when using Push Style C2 connections.`
mythicEnvInfo["mythic_server_grpc_port"] = `This specifies the port that the mythic_server's gRPC functionality listens on.
Translation containers will directly access this container and port when establishing gRPC functionality.
C2 Profile containers will directly access this container and port when using Push Style C2 connections.`
mythicEnv.SetDefault("mythic_server_host", "mythic_server")
mythicEnvInfo["mythic_server_host"] = `This specifies the ip/hostname for where the mythic server container executes. If this is 'mythic_server' or '127.0.0.1', then mythic-cli assumes this container is running locally. If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnvInfo["mythic_server_host"] = `This specifies the ip/hostname for where the mythic server container executes.
If this is 'mythic_server' or '127.0.0.1', then mythic-cli assumes this container is running locally.
If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnv.SetDefault("mythic_server_bind_localhost_only", true)
mythicEnvInfo["mythic_server_bind_localhost_only"] = `This specifies if the mythic_server container will expose the mythic_server_port and mythic_server_grpc_port on 0.0.0.0 or 127.0.0.1. If you have a remote agent container connecting to Mythic, you MUST set this to false so that the remote agent container can do file transfers with Mythic.`
mythicEnvInfo["mythic_server_bind_localhost_only"] = `This specifies if the mythic_server container will expose the mythic_server_port and mythic_server_grpc_port on 0.0.0.0 or 127.0.0.1.
If you have a remote agent container connecting to Mythic, you MUST set this to false so that the remote agent container can do file transfers with Mythic.`
mythicEnv.SetDefault("mythic_server_cpus", defaultNumberOfCPUs)
mythicEnvInfo["mythic_server_cpus"] = `Set this to limit the maximum number of CPUs this service is able to consume`
@@ -200,16 +223,22 @@ func setMythicConfigDefaultValues() {
mythicEnvInfo["mythic_server_mem_limit"] = `Set this to limit the maximum amount of RAM this service is able to consume`
mythicEnv.SetDefault("mythic_server_dynamic_ports", "7000-7010")
mythicEnvInfo["mythic_server_dynamic_ports"] = `These ports are exposed through the Docker container and provide access to SOCKS, Reverse Port Forward, and Interactive Tasking ports opened up by the Mythic Server. This is a comma-separated list of ranges, so you could do 7000-7010,7012,713-720`
mythicEnvInfo["mythic_server_dynamic_ports"] = `These ports are exposed through the Docker container and provide access to SOCKS, Reverse Port Forward, and Interactive Tasking ports opened up by the Mythic Server.
This is a comma-separated list of ranges, so you could do 7000-7010,7012,713-720`
mythicEnv.SetDefault("mythic_server_dynamic_ports_bind_localhost_only", true)
mythicEnvInfo["mythic_server_dynamic_ports_bind_localhost_only"] = `This specifies if the mythic_server container will expose the dynamic_ports on 0.0.0.0 or 127.0.0.1.`
mythicEnv.SetDefault("mythic_server_use_volume", false)
mythicEnvInfo["mythic_server_use_volume"] = `The mythic_server container saves uploaded and downloaded files. If this is True, then a docker volume is created and mounted into the container to host these pieces. If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnvInfo["mythic_server_use_volume"] = `The mythic_server container saves uploaded and downloaded files.
If this is True, then a docker volume is created and mounted into the container to host these pieces.
If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnv.SetDefault("mythic_server_use_build_context", false)
mythicEnvInfo["mythic_server_use_build_context"] = `The mythic_server container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io). Setting this to "true" means that the local Mythic/mythic-docker/Dockerfile is used to generate the image used for the mythic_server container instead of the hosted image. If you want to modify the local mythic_server code then you need to set this to true and uncomment the sections of the mythic-docker/Dockerfile that copy over the existing code and build it. If you don't do this then you won't see any of your changes take effect`
mythicEnvInfo["mythic_server_use_build_context"] = `The mythic_server container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io).
Setting this to "true" means that the local Mythic/mythic-docker/Dockerfile is used to generate the image used for the mythic_server container instead of the hosted image.
If you want to modify the local mythic_server code then you need to set this to true and uncomment the sections of the mythic-docker/Dockerfile that copy over the existing code and build it.
If you don't do this then you won't see any of your changes take effect`
mythicEnv.SetDefault("mythic_sync_cpus", defaultNumberOfCPUs)
mythicEnvInfo["mythic_sync_cpus"] = `Set this to limit the maximum number of CPUs this service is able to consume`
@@ -220,12 +249,18 @@ func setMythicConfigDefaultValues() {
mythicEnv.SetDefault("mythic_server_allow_invite_links", "false")
mythicEnvInfo["mythic_server_allow_invite_links"] = `This configures whether or not admins are allowed to create one-time-use invite links for users to join the server and register their own username/password combinations. They still need to be assigned to operations.'`
mythicEnv.SetDefault("mythic_server_docker_networking", "bridge")
mythicEnvInfo["mythic_server_docker_networking"] = `Configure how the mythic_server container is networked - the default is 'bridge' which means that ports must be explicitly exposed via mythic_server_dynamic_ports. The other option, 'host', means that the server will share networking with the host and not need explicit ports exposed. Either way, MYTHIC_SERVER_DYNAMIC_PORTS_BIND_LOCALHOST_ONLY and MYTHIC_SERVER_BIND_LOCALHOST_ONLY still determine if ports are bound to 0.0.0.0 or 127.0.0.1. If setting this to 'host', make sure you update the 'MYTHIC_SERVER_HOST' option as well to be the IP of the host machine (not localhost) and then restart Mythic to get the changes applied to docker compose. The containers will default to using localhost which won't work when the mythic_server is set to host networking.`
mythicEnv.SetDefault("mythic_docker_networking", "bridge")
mythicEnvInfo["mythic_docker_networking"] = `Configure how the mythic services are networked (everything except the 3rd party things you install) - the default is 'bridge' which means that ports must be explicitly exposed (mythic_server_dynamic_ports).
The other option, 'host', means that the services will share networking with the host and not need explicit ports exposed.
Either way, MYTHIC_SERVER_DYNAMIC_PORTS_BIND_LOCALHOST_ONLY and MYTHIC_SERVER_BIND_LOCALHOST_ONLY still determine if ports are bound to 0.0.0.0 or 127.0.0.1.
If setting this to 'host', make sure you update the 'MYTHIC_SERVER_HOST' option as well to be the IP of the host machine (not localhost) and then restart Mythic to get the changes applied to docker compose.
The containers will default to using localhost which won't work when the mythic_server is set to host networking.`
// postgres configuration ---------------------------------------------
mythicEnv.SetDefault("postgres_host", "mythic_postgres")
mythicEnvInfo["postgres_host"] = `This specifies the ip/hostname for where the postgres database container executes. If this is 'mythic_postgres' or '127.0.0.1', then mythic-cli assumes this container is running locally. If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnvInfo["postgres_host"] = `This specifies the ip/hostname for where the postgres database container executes.
If this is 'mythic_postgres' or '127.0.0.1', then mythic-cli assumes this container is running locally.
If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnv.SetDefault("postgres_port", 5432)
mythicEnvInfo["postgres_port"] = `This specifies the port that the Postgres database server listens on.`
@@ -249,20 +284,26 @@ func setMythicConfigDefaultValues() {
mythicEnvInfo["postgres_mem_limit"] = `Set this to limit the maximum amount of RAM this service is able to consume`
mythicEnv.SetDefault("postgres_use_volume", false)
mythicEnvInfo["postgres_use_volume"] = `The mythic_postgres container saves a database of everything that happens within Mythic. If this is True, then a docker volume is created and mounted into the container to host these pieces. If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnvInfo["postgres_use_volume"] = `The mythic_postgres container saves a database of everything that happens within Mythic.
If this is True, then a docker volume is created and mounted into the container to host these pieces.
If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnv.SetDefault("postgres_use_build_context", false)
mythicEnvInfo["postgres_use_build_context"] = `The mythic_postgres container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io). Setting this to "true" means that the local Mythic/postgres-docker/Dockerfile is used to generate the image used for the mythic_postgres container instead of the hosted image. `
mythicEnvInfo["postgres_use_build_context"] = `The mythic_postgres container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io).
Setting this to "true" means that the local Mythic/postgres-docker/Dockerfile is used to generate the image used for the mythic_postgres container instead of the hosted image. `
// rabbitmq configuration ---------------------------------------------
mythicEnv.SetDefault("rabbitmq_host", "mythic_rabbitmq")
mythicEnvInfo["rabbitmq_host"] = `This specifies the ip/hostname for where the RabbitMQ container executes. If this is 'rabbitmq_host' or '127.0.0.1', then mythic-cli assumes this container is running locally. If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnvInfo["rabbitmq_host"] = `This specifies the ip/hostname for where the RabbitMQ container executes.
If this is 'rabbitmq_host' or '127.0.0.1', then mythic-cli assumes this container is running locally.
If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnv.SetDefault("rabbitmq_port", 5672)
mythicEnvInfo["postgres_port"] = `This specifies the port that the RabbitMQ server listens on.`
mythicEnv.SetDefault("rabbitmq_bind_localhost_only", true)
mythicEnvInfo["rabbitmq_bind_localhost_only"] = `This specifies if the mythic_rabbitmq container will expose the rabbitmq_port on 0.0.0.0 or 127.0.0.1. If you have a remote agent container connecting to Mythic, you MUST set this to false so that the remote agent container can connect to Mythic.`
mythicEnvInfo["rabbitmq_bind_localhost_only"] = `This specifies if the mythic_rabbitmq container will expose the rabbitmq_port on 0.0.0.0 or 127.0.0.1.
If you have a remote agent container connecting to Mythic, you MUST set this to false so that the remote agent container can connect to Mythic.`
mythicEnv.SetDefault("rabbitmq_user", "mythic_user")
mythicEnvInfo["rabbitmq_user"] = `This is the user that all containers use to connect to RabbitMQ queues`
@@ -278,10 +319,13 @@ func setMythicConfigDefaultValues() {
mythicEnvInfo["rabbitmq_mem_limit"] = `Set this to limit the maximum amount of RAM this service is able to consume`
mythicEnv.SetDefault("rabbitmq_use_volume", false)
mythicEnvInfo["rabbitmq_use_volume"] = `The mythic_rabbitmq container saves data about the messages queues used and their stats. If this is True, then a docker volume is created and mounted into the container to host these pieces. If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnvInfo["rabbitmq_use_volume"] = `The mythic_rabbitmq container saves data about the messages queues used and their stats.
If this is True, then a docker volume is created and mounted into the container to host these pieces.
If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnv.SetDefault("rabbitmq_use_build_context", false)
mythicEnvInfo["rabbitmq_use_build_context"] = `The mythic_rabbitmq container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io). Setting this to "true" means that the local Mythic/rabbitmq-docker/Dockerfile is used to generate the image used for the mythic_rabbitmq container instead of the hosted image. `
mythicEnvInfo["rabbitmq_use_build_context"] = `The mythic_rabbitmq container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io).
Setting this to "true" means that the local Mythic/rabbitmq-docker/Dockerfile is used to generate the image used for the mythic_rabbitmq container instead of the hosted image. `
// jwt configuration ---------------------------------------------
mythicEnv.SetDefault("jwt_secret", utils.GenerateRandomPassword(30))
@@ -307,43 +351,60 @@ func setMythicConfigDefaultValues() {
mythicEnvInfo["hasura_mem_limit"] = `Set this to limit the maximum amount of RAM this service is able to consume`
mythicEnv.SetDefault("hasura_use_volume", false)
mythicEnvInfo["hasura_use_volume"] = `The mythic_graphql container has data about the roles within Mythic and their permissions for various graphQL endpoints. If this is True, then the internal settings are used from the built image. If this is false, then the local filesystem is mounted inside the container instead. If you want to make any changes to the Hasura permissions, columns, or actions, then you need to make sure you first set this to false and restart mythic_graphql so that your changes are saved to disk and loaded up each time properly.`
mythicEnvInfo["hasura_use_volume"] = `The mythic_graphql container has data about the roles within Mythic and their permissions for various graphQL endpoints.
If this is True, then the internal settings are used from the built image.
If this is false, then the local filesystem is mounted inside the container instead.
If you want to make any changes to the Hasura permissions, columns, or actions, then you need to make sure you first set this to false and restart mythic_graphql so that your changes are saved to disk and loaded up each time properly.`
mythicEnv.SetDefault("hasura_use_build_context", false)
mythicEnvInfo["hasura_use_build_context"] = `The mythic_graphql container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io). Setting this to "true" means that the local Mythic/hasura-docker/Dockerfile is used to generate the image used for the mythic_graphql container instead of the hosted image.`
mythicEnvInfo["hasura_use_build_context"] = `The mythic_graphql container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io).
Setting this to "true" means that the local Mythic/hasura-docker/Dockerfile is used to generate the image used for the mythic_graphql container instead of the hosted image.`
// docker-compose configuration ---------------------------------------------
mythicEnv.SetDefault("COMPOSE_PROJECT_NAME", "mythic")
mythicEnvInfo["compose_project_name"] = `This is the project name for Docker Compose - it sets the prefix of the container names and shouldn't be changed`
mythicEnv.SetDefault("REBUILD_ON_START", false)
mythicEnvInfo["rebuild_on_start"] = `This identifies if a container's backing image should be re-built (or re-fetched) each time you start the container. This can cause agent and c2 profile containers to have their volumes wiped on each start (and thus deleting any changes). This also drastically increases the start time for Mythic overall. This should only be needed if you're doing a bunch of development on Mythic itself. If you need to rebuild a specific container, you should use './mythic-cli build [container name]' instead to just rebuild that one container`
mythicEnvInfo["rebuild_on_start"] = `This identifies if a container's backing image should be re-built (or re-fetched) each time you start the container.
This can cause agent and c2 profile containers to have their volumes wiped on each start (and thus deleting any changes).
This also drastically increases the start time for Mythic overall.
This should only be needed if you're doing a bunch of development on Mythic itself.
If you need to rebuild a specific container, you should use './mythic-cli build [container name]' instead to just rebuild that one container`
// Mythic instance configuration ---------------------------------------------
mythicEnv.SetDefault("mythic_admin_user", "mythic_admin")
mythicEnvInfo["mythic_admin_user"] = `This configures the name of the first user in Mythic when Mythic starts for the first time. After the first time Mythic starts, this value is unused.`
mythicEnvInfo["mythic_admin_user"] = `This configures the name of the first user in Mythic when Mythic starts for the first time.
After the first time Mythic starts, this value is unused.`
mythicEnv.SetDefault("mythic_admin_password", utils.GenerateRandomPassword(30))
mythicEnvInfo["mythic_admin_password"] = `This randomly generated password is used when Mythic first starts to set the password for the mythic_admin_user account. After the first time Mythic starts, this value is unused`
mythicEnvInfo["mythic_admin_password"] = `This randomly generated password is used when Mythic first starts to set the password for the mythic_admin_user account.
After the first time Mythic starts, this value is unused`
mythicEnv.SetDefault("default_operation_name", "Operation Chimera")
mythicEnvInfo["default_operation_name"] = `This is used to name the initial operation created for the mythic_admin account. After the first time Mythic starts, this value is unused`
mythicEnvInfo["default_operation_name"] = `This is used to name the initial operation created for the mythic_admin account.
After the first time Mythic starts, this value is unused`
mythicEnv.SetDefault("allowed_ip_blocks", "0.0.0.0/0,::/0")
mythicEnvInfo["allowed_ip_blocks"] = `This comma-separated set of HOST-ONLY CIDR ranges specifies where valid logins can come from. These values are used by mythic_server to block potential downloads as well as by mythic_nginx to block connections from invalid addresses as well.`
mythicEnvInfo["allowed_ip_blocks"] = `This comma-separated set of HOST-ONLY CIDR ranges specifies where valid logins can come from.
These values are used by mythic_server to block potential downloads as well as by mythic_nginx to block connections from invalid addresses as well.`
mythicEnv.SetDefault("default_operation_webhook_url", "")
mythicEnvInfo["default_operation_webhook_url"] = `If an operation doesn't specify their own webhook URL, then this value is used. You must instal a webhook container to have access to webhooks.`
mythicEnvInfo["default_operation_webhook_url"] = `If an operation doesn't specify their own webhook URL, then this value is used.
You must install a webhook container to have access to webhooks.`
mythicEnv.SetDefault("default_operation_webhook_channel", "")
mythicEnvInfo["default_operation_webhook_channel"] = `If an operation doesn't specify their own webhook channel, then this value is used. You must install a webhook container to have access to webhooks.`
mythicEnvInfo["default_operation_webhook_channel"] = `If an operation doesn't specify their own webhook channel, then this value is used.
You must install a webhook container to have access to webhooks.`
// jupyter configuration ---------------------------------------------
mythicEnv.SetDefault("jupyter_port", 8888)
mythicEnvInfo["jupyter_port"] = `This specifies the port for the mythic_jupyter container to expose outside of its container. This is typically accessed through the nginx proxy via /jupyter`
mythicEnvInfo["jupyter_port"] = `This specifies the port for the mythic_jupyter container to expose outside of its container.
This is typically accessed through the nginx proxy via /jupyter`
mythicEnv.SetDefault("jupyter_host", "mythic_jupyter")
mythicEnvInfo["jupyter_host"] = `This specifies the ip/hostname for where the Jupyter container executes. If this is 'jupyter_host' or '127.0.0.1', then mythic-cli assumes this container is running locally. If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnvInfo["jupyter_host"] = `This specifies the ip/hostname for where the Jupyter container executes.
If this is 'jupyter_host' or '127.0.0.1', then mythic-cli assumes this container is running locally.
If it's anything else, mythic-cli will not spin up this container as it assumes it lives elsewhere`
mythicEnv.SetDefault("jupyter_token", utils.GenerateRandomPassword(30))
mythicEnvInfo["jupyter_token"] = `This value is used to authenticate to the Jupyter instance via the /jupyter route in the React UI`
@@ -358,15 +419,21 @@ func setMythicConfigDefaultValues() {
mythicEnvInfo["jupyter_bind_localhost_only"] = `This specifies if the mythic_jupyter container will expose the jupyter_port on 0.0.0.0 or 127.0.0.1. `
mythicEnv.SetDefault("jupyter_use_volume", false)
mythicEnvInfo["jupyter_use_volume"] = `The mythic_jupyter container saves data about script examples. If this is True, then a docker volume is created and mounted into the container to host these pieces. If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnvInfo["jupyter_use_volume"] = `The mythic_jupyter container saves data about script examples.
If this is True, then a docker volume is created and mounted into the container to host these pieces.
If this is false, then the local filesystem is mounted inside the container instead. `
mythicEnv.SetDefault("jupyter_use_build_context", false)
mythicEnvInfo["jupyter_use_build_context"] = `The mythic_jupyter container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io). Setting this to "true" means that the local Mythic/jupyter-docker/Dockerfile is used to generate the image used for the mythic_jupyter container instead of the hosted image.`
mythicEnvInfo["jupyter_use_build_context"] = `The mythic_jupyter container by default pulls configuration from a pre-compiled Docker image hosted on GitHub's Container Registry (ghcr.io).
Setting this to "true" means that the local Mythic/jupyter-docker/Dockerfile is used to generate the image used for the mythic_jupyter container instead of the hosted image.`
// debugging help ---------------------------------------------
mythicEnv.SetDefault("postgres_debug", false)
mythicEnv.SetDefault("mythic_react_debug", false)
mythicEnvInfo["mythic_react_debug"] = `Setting this to true switches the React UI from using a pre-built React UI to a live hot-reloading development server. You should only need to do this if you're planning on working on the Mythic UI. Once you're doing making changes to the UI, you can run 'sudo ./mythic-cli build_ui' to compile your changes and save them to the mythic-react-docker folder. Assuming you have mythic_react_use_volume set to false, then when you disable debugging, you'll be using the newly compiled version of the UI`
mythicEnvInfo["mythic_react_debug"] = `Setting this to true switches the React UI from using a pre-built React UI to a live hot-reloading development server.
You should only need to do this if you're planning on working on the Mythic UI.
Once you're doing making changes to the UI, you can run 'sudo ./mythic-cli build_ui' to compile your changes and save them to the mythic-react-docker folder.
Assuming you have mythic_react_use_volume set to false, then when you disable debugging, you'll be using the newly compiled version of the UI`
// installed service configuration ---------------------------------------------
mythicEnv.SetDefault("installed_service_cpus", "1")
@@ -455,7 +522,10 @@ func parseMythicEnvironmentVariables() {
}
}
mythicEnv.Set("global_docker_latest", MythicDockerLatest)
mythicEnvInfo["global_docker_latest"] = `This is the latest Docker Image version available for all Mythic services (mythic_server, mythic_postgres, mythic-cli, etc). This is determined by the tag on the Mythic branch and stamped into mythic-cli. Even if you change or remove this locally, mythic-cli will always put it back to what it was. For each of the main Mythic services, if you set their *_use_build_context to false, then it's this specified Docker image version that will be fetched and used.`
mythicEnvInfo["global_docker_latest"] = `This is the latest Docker Image version available for all Mythic services (mythic_server, mythic_postgres, mythic-cli, etc).
This is determined by the tag on the Mythic branch and stamped into mythic-cli.
Even if you change or remove this locally, mythic-cli will always put it back to what it was.
For each of the main Mythic services, if you set their *_use_build_context to false, then it's this specified Docker image version that will be fetched and used.`
writeMythicEnvironmentVariables()
}
func writeMythicEnvironmentVariables() {
+1 -1
View File
@@ -4,5 +4,5 @@ package config
var (
// Version Mythic CLI version
Version = "v0.3.3"
Version = "v0.3.4"
)
+257 -104
View File
@@ -50,16 +50,39 @@ func AddMythicService(service string, removeVolume bool) {
if mythicEnv.GetString("postgres_mem_limit") != "" {
pStruct["mem_limit"] = mythicEnv.GetString("postgres_mem_limit")
}
if mythicEnv.GetBool("postgres_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${POSTGRES_PORT}:${POSTGRES_PORT}",
if mythicEnv.GetString("mythic_docker_networking") == "bridge" {
if mythicEnv.GetBool("postgres_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${POSTGRES_PORT}:${POSTGRES_PORT}",
}
} else {
pStruct["ports"] = []string{
"${POSTGRES_PORT}:${POSTGRES_PORT}",
}
}
pStruct["command"] = "postgres -c \"max_connections=100\" -p ${POSTGRES_PORT} -c config_file=/etc/postgresql.conf"
delete(pStruct, "network_mode")
delete(pStruct, "extra_hosts")
} else {
pStruct["ports"] = []string{
"${POSTGRES_PORT}:${POSTGRES_PORT}",
delete(pStruct, "ports")
pStruct["network_mode"] = "host"
pStruct["extra_hosts"] = []string{
"mythic_server:127.0.0.1",
"mythic_rabbitmq:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_nginx:127.0.0.1",
"mythic_react:127.0.0.1",
"mythic_documentation:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_jupyter:127.0.0.1",
"mythic_postgres:127.0.0.1",
}
if mythicEnv.GetBool("postgres_bind_localhost_only") {
pStruct["command"] = "postgres -c \"max_connections=100\" -p ${POSTGRES_PORT} -c config_file=/etc/postgresql.conf -c \"listen_addresses='localhost'\""
} else {
pStruct["command"] = "postgres -c \"max_connections=100\" -p ${POSTGRES_PORT} -c config_file=/etc/postgresql.conf"
}
}
pStruct["command"] = "postgres -c \"max_connections=100\" -p ${POSTGRES_PORT} -c config_file=/etc/postgresql.conf"
environment := []string{
"POSTGRES_DB=${POSTGRES_DB}",
"POSTGRES_USER=${POSTGRES_USER}",
@@ -97,19 +120,49 @@ func AddMythicService(service string, removeVolume bool) {
} else {
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
}
if mythicEnv.GetBool("documentation_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${DOCUMENTATION_PORT}:${DOCUMENTATION_PORT}",
if mythicEnv.GetString("mythic_docker_networking") == "bridge" {
if mythicEnv.GetBool("documentation_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${DOCUMENTATION_PORT}:${DOCUMENTATION_PORT}",
}
} else {
pStruct["ports"] = []string{
"${DOCUMENTATION_PORT}:${DOCUMENTATION_PORT}",
}
}
pStruct["environment"] = []string{
"DOCUMENTATION_PORT=${DOCUMENTATION_PORT}",
}
delete(pStruct, "network_mode")
delete(pStruct, "extra_hosts")
} else {
pStruct["ports"] = []string{
"${DOCUMENTATION_PORT}:${DOCUMENTATION_PORT}",
delete(pStruct, "ports")
pStruct["network_mode"] = "host"
pStruct["extra_hosts"] = []string{
"mythic_server:127.0.0.1",
"mythic_rabbitmq:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_nginx:127.0.0.1",
"mythic_react:127.0.0.1",
"mythic_documentation:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_jupyter:127.0.0.1",
"mythic_postgres:127.0.0.1",
}
if mythicEnv.GetBool("documentation_bind_localhost_only") {
pStruct["environment"] = []string{
"DOCUMENTATION_PORT=${DOCUMENTATION_PORT}",
"HUGO_BIND=127.0.0.1",
}
} else {
pStruct["environment"] = []string{
"DOCUMENTATION_PORT=${DOCUMENTATION_PORT}",
"HUGO_BIND=0.0.0.0",
}
}
}
pStruct["environment"] = []string{
"DOCUMENTATION_PORT=${DOCUMENTATION_PORT}",
}
if !mythicEnv.GetBool("documentation_use_volume") {
pStruct["volumes"] = []string{
"./documentation-docker/:/src",
@@ -154,48 +207,51 @@ func AddMythicService(service string, removeVolume bool) {
"MYTHIC_ACTIONS_URL_BASE=http://${MYTHIC_SERVER_HOST}:${MYTHIC_SERVER_PORT}/api/v1.4",
"HASURA_GRAPHQL_CONSOLE_ASSETS_DIR=/srv/console-assets",
}
if _, ok := pStruct["environment"]; ok {
pStruct["environment"] = utils.UpdateEnvironmentVariables(pStruct["environment"].([]interface{}), environment)
} else {
pStruct["environment"] = environment
}
//if _, ok := pStruct["environment"]; ok {
// pStruct["environment"] = utils.UpdateEnvironmentVariables(pStruct["environment"].([]interface{}), environment)
//} else {
// pStruct["environment"] = environment
//}
if mythicEnv.GetString("mythic_docker_networking") == "bridge" {
if mythicEnv.GetBool("hasura_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${HASURA_PORT}:${HASURA_PORT}",
}
if mythicEnv.GetBool("hasura_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${HASURA_PORT}:${HASURA_PORT}",
} else {
pStruct["ports"] = []string{
"${HASURA_PORT}:${HASURA_PORT}",
}
}
delete(pStruct, "network_mode")
delete(pStruct, "extra_hosts")
} else {
pStruct["ports"] = []string{
"${HASURA_PORT}:${HASURA_PORT}",
delete(pStruct, "ports")
pStruct["network_mode"] = "host"
pStruct["extra_hosts"] = []string{
"mythic_server:127.0.0.1",
"mythic_rabbitmq:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_nginx:127.0.0.1",
"mythic_react:127.0.0.1",
"mythic_documentation:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_jupyter:127.0.0.1",
"mythic_postgres:127.0.0.1",
}
if mythicEnv.GetBool("hasura_bind_localhost_only") {
environment = append(environment, "HASURA_GRAPHQL_SERVER_HOST=127.0.0.1")
} else {
environment = append(environment, "HASURA_GRAPHQL_SERVER_HOST=*")
}
}
pStruct["environment"] = environment
if !mythicEnv.GetBool("hasura_use_volume") {
pStruct["volumes"] = []string{
"./hasura-docker/metadata:/metadata",
}
} else {
delete(pStruct, "volumes")
/*
pStruct["volumes"] = []string{
"mythic_graphql_volume:/metadata",
}
*/
}
/*
if _, ok := volumes["mythic_graphql"]; !ok {
volumes["mythic_graphql_volume"] = map[string]interface{}{
"name": "mythic_graphql_volume",
}
}
*/
if mythicEnv.GetString("mythic_server_docker_networking") == "host" {
pStruct["extra_hosts"] = []string{
"mythic_server:${MYTHIC_SERVER_HOST}",
}
} else {
delete(pStruct, "extra_hosts")
}
case "mythic_nginx":
if mythicEnv.GetBool("nginx_use_build_context") {
@@ -245,17 +301,43 @@ func AddMythicService(service string, removeVolume bool) {
finalNginxEnv = append(finalNginxEnv, val)
}
}
pStruct["environment"] = finalNginxEnv
if mythicEnv.GetBool("nginx_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${NGINX_PORT}:${NGINX_PORT}",
for _, val := range []string{"NGINX_BIND_IPV4=0.0.0.0", "NGINX_BIND_IPV6=[::]", "NGINX_BIND_IPV4=127.0.0.1", "NGINX_BIND_IPV6=[::1]"} {
finalNginxEnv = utils.RemoveStringFromSliceNoOrder(finalNginxEnv, val)
}
if mythicEnv.GetString("mythic_docker_networking") == "bridge" {
if mythicEnv.GetBool("nginx_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${NGINX_PORT}:${NGINX_PORT}",
}
} else {
pStruct["ports"] = []string{
"${NGINX_PORT}:${NGINX_PORT}",
}
}
finalNginxEnv = append(finalNginxEnv, "NGINX_BIND_IPV4=0.0.0.0", "NGINX_BIND_IPV6=[::]")
delete(pStruct, "network_mode")
delete(pStruct, "extra_hosts")
} else {
pStruct["ports"] = []string{
"${NGINX_PORT}:${NGINX_PORT}",
delete(pStruct, "ports")
pStruct["network_mode"] = "host"
pStruct["extra_hosts"] = []string{
"mythic_server:127.0.0.1",
"mythic_rabbitmq:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_nginx:127.0.0.1",
"mythic_react:127.0.0.1",
"mythic_documentation:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_jupyter:127.0.0.1",
"mythic_postgres:127.0.0.1",
}
if mythicEnv.GetBool("nginx_bind_localhost_only") {
finalNginxEnv = append(finalNginxEnv, "NGINX_BIND_IPV4=127.0.0.1", "NGINX_BIND_IPV6=[::1]")
} else {
finalNginxEnv = append(finalNginxEnv, "NGINX_BIND_IPV4=0.0.0.0", "NGINX_BIND_IPV6=[::]")
}
}
pStruct["environment"] = finalNginxEnv
if !mythicEnv.GetBool("nginx_use_volume") {
pStruct["volumes"] = []string{
"./nginx-docker/ssl:/etc/ssl/private",
@@ -275,13 +357,6 @@ func AddMythicService(service string, removeVolume bool) {
"name": "mythic_nginx_volume_ssl",
}
}
if mythicEnv.GetString("mythic_server_docker_networking") == "host" {
pStruct["extra_hosts"] = []string{
"mythic_server:${MYTHIC_SERVER_HOST}",
}
} else {
delete(pStruct, "extra_hosts")
}
case "mythic_rabbitmq":
if mythicEnv.GetBool("rabbitmq_use_build_context") {
@@ -293,32 +368,10 @@ func AddMythicService(service string, removeVolume bool) {
} else {
pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest"))
}
pStruct["cpus"] = mythicEnv.GetInt("RABBITMQ_CPUS")
if mythicEnv.GetString("rabbitmq_mem_limit") != "" {
pStruct["mem_limit"] = mythicEnv.GetString("rabbitmq_mem_limit")
}
if mythicEnv.GetBool("rabbitmq_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${RABBITMQ_PORT}:${RABBITMQ_PORT}",
}
if mythicEnv.GetString("mythic_server_docker_networking") == "host" {
pStruct["ports"] = []string{
"127.0.0.1:${RABBITMQ_PORT}:${RABBITMQ_PORT}",
"127.0.0.1:15672:15672",
}
}
} else {
pStruct["ports"] = []string{
"${RABBITMQ_PORT}:${RABBITMQ_PORT}",
}
if mythicEnv.GetString("mythic_server_docker_networking") == "host" {
pStruct["ports"] = []string{
"${RABBITMQ_PORT}:${RABBITMQ_PORT}",
"127.0.0.1:15672:15672",
}
}
}
environment := []string{
"RABBITMQ_USER=${RABBITMQ_USER}",
"RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD}",
@@ -339,6 +392,39 @@ func AddMythicService(service string, removeVolume bool) {
finalRabbitEnv = append(finalRabbitEnv, val)
}
}
if mythicEnv.GetString("mythic_docker_networking") == "bridge" {
if mythicEnv.GetBool("rabbitmq_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${RABBITMQ_PORT}:${RABBITMQ_PORT}",
}
} else {
pStruct["ports"] = []string{
"${RABBITMQ_PORT}:${RABBITMQ_PORT}",
}
}
delete(pStruct, "network_mode")
delete(pStruct, "extra_hosts")
} else {
delete(pStruct, "ports")
pStruct["network_mode"] = "host"
pStruct["extra_hosts"] = []string{
"mythic_server:127.0.0.1",
"mythic_rabbitmq:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_nginx:127.0.0.1",
"mythic_react:127.0.0.1",
"mythic_documentation:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_jupyter:127.0.0.1",
"mythic_postgres:127.0.0.1",
}
if mythicEnv.GetBool("rabbitmq_bind_localhost_only") {
environment = append(environment, "RABBITMQ_NODE_IP_ADDRESS=127.0.0.1", "RABBITMQ_NODE_PORT=${RABBITMQ_PORT}")
} else {
environment = append(environment, "RABBITMQ_NODE_IP_ADDRESS=0.0.0.0", "RABBITMQ_NODE_PORT=${RABBITMQ_PORT}")
}
}
pStruct["environment"] = finalRabbitEnv
if !mythicEnv.GetBool("rabbitmq_use_volume") {
pStruct["volumes"] = []string{
@@ -407,18 +493,49 @@ func AddMythicService(service string, removeVolume bool) {
"name": "mythic_react_volume_public",
}
}
if mythicEnv.GetBool("mythic_react_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${MYTHIC_REACT_PORT}:${MYTHIC_REACT_PORT}",
if mythicEnv.GetString("mythic_docker_networking") == "bridge" {
if mythicEnv.GetBool("mythic_react_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${MYTHIC_REACT_PORT}:${MYTHIC_REACT_PORT}",
}
} else {
pStruct["ports"] = []string{
"${MYTHIC_REACT_PORT}:${MYTHIC_REACT_PORT}",
}
}
pStruct["environment"] = []string{
"MYTHIC_REACT_PORT=${MYTHIC_REACT_PORT}",
"MYTHIC_REACT_BIND_IPV4=0.0.0.0",
}
delete(pStruct, "network_mode")
delete(pStruct, "extra_hosts")
} else {
pStruct["ports"] = []string{
"${MYTHIC_REACT_PORT}:${MYTHIC_REACT_PORT}",
delete(pStruct, "ports")
pStruct["network_mode"] = "host"
pStruct["extra_hosts"] = []string{
"mythic_server:127.0.0.1",
"mythic_rabbitmq:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_nginx:127.0.0.1",
"mythic_react:127.0.0.1",
"mythic_documentation:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_jupyter:127.0.0.1",
"mythic_postgres:127.0.0.1",
}
if mythicEnv.GetBool("mythic_react_bind_localhost_only") {
pStruct["environment"] = []string{
"MYTHIC_REACT_PORT=${MYTHIC_REACT_PORT}",
"MYTHIC_REACT_BIND_IPV4=127.0.0.1",
}
} else {
pStruct["environment"] = []string{
"MYTHIC_REACT_PORT=${MYTHIC_REACT_PORT}",
"MYTHIC_REACT_BIND_IPV4=0.0.0.0",
}
}
}
pStruct["environment"] = []string{
"MYTHIC_REACT_PORT=${MYTHIC_REACT_PORT}",
}
case "mythic_jupyter":
if mythicEnv.GetBool("jupyter_use_build_context") {
pStruct["build"] = map[string]interface{}{
@@ -434,20 +551,49 @@ func AddMythicService(service string, removeVolume bool) {
if mythicEnv.GetString("jupyter_mem_limit") != "" {
pStruct["mem_limit"] = mythicEnv.GetString("jupyter_mem_limit")
}
if mythicEnv.GetBool("jupyter_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${JUPYTER_PORT}:${JUPYTER_PORT}",
if mythicEnv.GetString("mythic_docker_networking") == "bridge" {
if mythicEnv.GetBool("jupyter_bind_localhost_only") {
pStruct["ports"] = []string{
"127.0.0.1:${JUPYTER_PORT}:${JUPYTER_PORT}",
}
} else {
pStruct["ports"] = []string{
"${JUPYTER_PORT}:${JUPYTER_PORT}",
}
}
delete(pStruct, "network_mode")
delete(pStruct, "extra_hosts")
pStruct["environment"] = []string{
"JUPYTER_TOKEN=${JUPYTER_TOKEN}",
"CHOWN_EXTRA=/projects",
"CHOWN_EXTRA_OPTS=-R",
}
} else {
pStruct["ports"] = []string{
"${JUPYTER_PORT}:${JUPYTER_PORT}",
delete(pStruct, "ports")
pStruct["network_mode"] = "host"
pStruct["extra_hosts"] = []string{
"mythic_server:127.0.0.1",
"mythic_rabbitmq:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_nginx:127.0.0.1",
"mythic_react:127.0.0.1",
"mythic_documentation:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_jupyter:127.0.0.1",
"mythic_postgres:127.0.0.1",
}
}
pStruct["environment"] = []string{
"JUPYTER_TOKEN=${JUPYTER_TOKEN}",
"CHOWN_EXTRA=/projects",
"CHOWN_EXTRA_OPTS=-R",
environment := []string{
"JUPYTER_TOKEN=${JUPYTER_TOKEN}",
"CHOWN_EXTRA=/projects",
"CHOWN_EXTRA_OPTS=-R",
"JUPYTER_PORT=${JUPYTER_PORT}",
}
if mythicEnv.GetBool("jupyter_bind_localhost_only") {
environment = append(environment, "JUPYTER_IP=127.0.0.1")
} else {
environment = append(environment, "JUPYTER_IP=0.0.0.0")
}
pStruct["environment"] = environment
}
pStruct["user"] = "root"
/*
@@ -508,10 +654,10 @@ func AddMythicService(service string, removeVolume bool) {
"NGINX_HOST=${NGINX_HOST}",
"MYTHIC_SERVER_DYNAMIC_PORTS=${MYTHIC_SERVER_DYNAMIC_PORTS}",
"MYTHIC_SERVER_DYNAMIC_PORTS_BIND_LOCALHOST_ONLY=${MYTHIC_SERVER_DYNAMIC_PORTS_BIND_LOCALHOST_ONLY}",
"MYTHIC_SERVER_DOCKER_NETWORKING=${MYTHIC_SERVER_DOCKER_NETWORKING}",
"MYTHIC_DOCKER_NETWORKING=${MYTHIC_DOCKER_NETWORKING}",
"GLOBAL_SERVER_NAME=${GLOBAL_SERVER_NAME}",
}
if mythicEnv.GetString("mythic_server_docker_networking") == "bridge" {
if mythicEnv.GetString("mythic_docker_networking") == "bridge" {
mythicServerPorts := []string{
"${MYTHIC_SERVER_PORT}:${MYTHIC_SERVER_PORT}",
"${MYTHIC_SERVER_GRPC_PORT}:${MYTHIC_SERVER_GRPC_PORT}",
@@ -538,8 +684,15 @@ func AddMythicService(service string, removeVolume bool) {
delete(pStruct, "ports")
pStruct["network_mode"] = "host"
pStruct["extra_hosts"] = []string{
"mythic_postgres:127.0.0.1",
"mythic_server:127.0.0.1",
"mythic_rabbitmq:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_nginx:127.0.0.1",
"mythic_react:127.0.0.1",
"mythic_documentation:127.0.0.1",
"mythic_graphql:127.0.0.1",
"mythic_jupyter:127.0.0.1",
"mythic_postgres:127.0.0.1",
}
}
+8 -2
View File
@@ -10,6 +10,7 @@ import (
"os"
"path"
"path/filepath"
"slices"
"strings"
)
@@ -66,11 +67,16 @@ func UpdateEnvironmentVariables(originalList []interface{}, updates []string) []
}
}
if !found {
finalList = append(finalList, entry.(string))
if !slices.Contains(finalList, entry.(string)) {
finalList = append(finalList, entry.(string))
}
}
}
for _, update := range updates {
finalList = append(finalList, update)
if !slices.Contains(finalList, update) {
finalList = append(finalList, update)
}
}
return finalList
}
+1 -1
View File
@@ -10,6 +10,7 @@ require (
github.com/rabbitmq/amqp091-go v1.9.0
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81
golang.org/x/mod v0.16.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -50,7 +51,6 @@ require (
go.opentelemetry.io/otel/sdk v1.25.0 // indirect
go.opentelemetry.io/otel/trace v1.25.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.19.0 // indirect
+1 -1
View File
@@ -1 +1 @@
3.3.0-rc22
3.3.0-rc23
+11
View File
@@ -231,6 +231,12 @@ type Mutation {
): dynamicQueryOutput
}
type Query {
eventingTestFile(
file_contents: String!
): eventingTestFileOutput
}
type Mutation {
eventingTriggerCancel(
eventgroupinstance_id: Int!
@@ -1068,3 +1074,8 @@ type testProxyOutput {
error: String
}
type eventingTestFileOutput {
status: String!
error: String
}
+11
View File
@@ -331,6 +331,16 @@ actions:
- role: operation_admin
- role: mythic_admin
- role: developer
- name: eventingTestFile
definition:
kind: ""
handler: '{{MYTHIC_ACTIONS_URL_BASE}}/eventing_test_file_webhook'
forward_client_headers: true
permissions:
- role: operator
- role: operation_admin
- role: mythic_admin
- role: developer
- name: eventingTriggerCancel
definition:
kind: synchronous
@@ -831,4 +841,5 @@ custom_types:
- name: containerWriteFileOutput
- name: eventingTriggerRetryFromStepOutput
- name: testProxyOutput
- name: eventingTestFileOutput
scalars: []
+3 -1
View File
@@ -13,8 +13,10 @@ WORKDIR /projects
ENV JUPYTERHUB_SERVICE_PREFIX "/jupyter/"
ENV JUPYTER_TOKEN "mythic"
ENV JUPYTER_IP "0.0.0.0"
ENV JUPYTER_PORT "8888"
COPY ["jupyter/", "."]
CMD start.sh jupyter lab --ServerApp.open_browser=false --IdentityProvider.token=${JUPYTER_TOKEN:-mythic} --ServerApp.base_url="/jupyter" --ServerApp.default_url="/jupyter"
CMD start.sh jupyter lab --ServerApp.open_browser=false --IdentityProvider.token=${JUPYTER_TOKEN:-mythic} --ServerApp.base_url="/jupyter" --ServerApp.default_url="/jupyter" --port=${JUPYTER_PORT} --ip=${JUPYTER_IP}
# sudo docker run -p 8888:8888 -v `pwd`/jupyter:/projects jupyter
+1 -1
View File
@@ -1 +1 @@
3.3.0-rc22
3.3.0-rc23
@@ -8,7 +8,6 @@ import (
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
"github.com/mitchellh/mapstructure"
"slices"
"time"
)
@@ -165,7 +164,7 @@ func getTriggerData(triggerMetadata map[string]interface{}) map[string]interface
logging.LogError(err, "failed to marshal payload into bytes for saving trigger metadata")
return triggerData
}
err = mapstructure.Decode(triggerBytes, &triggerData)
err = json.Unmarshal(triggerBytes, &triggerData)
if err != nil {
logging.LogError(err, "failed to decode payload into struct")
}
@@ -525,7 +524,7 @@ func updateAllRemainingStepInstances(eventGroupInstanceId int, status string) er
return err
}
for i, _ := range eventStepInstances {
if eventStepInstances[i].Status == EventGroupInstanceStatusQueued {
if eventStepInstances[i].Status == EventGroupInstanceStatusQueued || eventStepInstances[i].Status == EventGroupInstanceStatusRunning {
eventStepInstances[i].Status = status
eventStepInstances[i].EndTimestamp.Time = time.Now().UTC()
eventStepInstances[i].EndTimestamp.Valid = true
+23
View File
@@ -116,6 +116,9 @@ func Ingest(fileContents []byte) (databaseStructs.EventGroup, error) {
return databaseEventGroup, err
}
}
if eventGroup.Name == "" {
return databaseEventGroup, errors.New("no event name supplied")
}
databaseEventGroup.Name = eventGroup.Name
databaseEventGroup.TriggerData = GetMythicJSONTextFromStruct(eventGroup.TriggerData)
databaseEventGroup.Trigger = eventGroup.Trigger
@@ -128,6 +131,9 @@ func Ingest(fileContents []byte) (databaseStructs.EventGroup, error) {
}
for i, _ := range eventGroup.Steps {
databaseEventStep := databaseStructs.EventStep{}
if eventGroup.Steps[i].Name == "" {
return databaseEventGroup, errors.New("no step name supplied")
}
databaseEventStep.Name = eventGroup.Steps[i].Name
databaseEventStep.Description = eventGroup.Steps[i].Description
databaseEventStep.Environment = GetMythicJSONTextFromStruct(eventGroup.Steps[i].Environment)
@@ -229,3 +235,20 @@ func EnsureActions(eventGroup *databaseStructs.EventGroup) error {
}
return nil
}
func EnsureActionDataForAction(eventGroup *databaseStructs.EventGroup) error {
for i, _ := range eventGroup.Steps {
switch eventGroup.Steps[i].Action {
case ActionPayloadCreate:
case ActionCreateTask:
case ActionInterceptResponse:
case ActionInterceptTask:
case ActionConditionalCheck:
case ActionCustomFunction:
case ActionCreateCallback:
}
}
if len(eventGroup.Steps) == 0 {
return errors.New(fmt.Sprintf("No steps detected for event group"))
}
return nil
}
@@ -364,7 +364,7 @@ func (g *cbGraph) GetBFSPath(sourceId int, destinationId int) []cbGraphAdjMatrix
return nil
}
if existingBFSpath := BFSCache.GetPath(sourceId, destinationId); existingBFSpath != nil {
logging.LogDebug("returning a cachedBFS path", "sourceID", sourceId, "destinationID", destinationId)
//logging.LogDebug("returning a cachedBFS path", "sourceID", sourceId, "destinationID", destinationId)
return existingBFSpath
}
// start with a fake-ish entry that we won't include in the final path
+2 -2
View File
@@ -62,7 +62,7 @@ func Initialize() {
mythicEnv.SetDefault("mythic_server_bind_localhost_only", true)
mythicEnv.SetDefault("mythic_server_dynamic_ports", "7000-7010")
mythicEnv.SetDefault("mythic_server_dynamic_ports_bind_localhost_only", true)
mythicEnv.SetDefault("mythic_server_docker_networking", "bridge")
mythicEnv.SetDefault("mythic_docker_networking", "bridge")
mythicEnv.SetDefault("mythic_server_grpc_port", 17444)
mythicEnv.SetDefault("mythic_admin_user", "mythic_admin")
mythicEnv.SetDefault("mythic_admin_password", "mythic_password")
@@ -158,7 +158,7 @@ func setConfigFromEnv(mythicEnv *viper.Viper) {
}
}
MythicConfig.ServerDynamicPortsBindLocalhostOnly = mythicEnv.GetBool("mythic_server_dynamic_ports_bind_localhost_only")
MythicConfig.ServerDockerNetworking = mythicEnv.GetString("mythic_server_docker_networking")
MythicConfig.ServerDockerNetworking = mythicEnv.GetString("mythic_docker_networking")
MythicConfig.AdminUser = mythicEnv.GetString("mythic_admin_user")
MythicConfig.AdminPassword = mythicEnv.GetString("mythic_admin_password")
MythicConfig.DefaultOperationName = mythicEnv.GetString("default_operation_name")
@@ -0,0 +1,74 @@
package webcontroller
import (
"github.com/gin-gonic/gin"
"github.com/its-a-feature/Mythic/eventing"
"github.com/its-a-feature/Mythic/logging"
"net/http"
)
type EventingTestFileWebhookInput struct {
Input EventingTestFileInput `json:"input" binding:"required"`
}
type EventingTestFileInput struct {
FileContents string `json:"file_contents" binding:"required"`
}
type EventingTestFileWebhookResponse struct {
Status string `json:"status"`
Error string `json:"error"`
}
func EventingTestFileWebhook(c *gin.Context) {
var input EventingTestFileWebhookInput
err := c.ShouldBindJSON(&input)
if err != nil {
logging.LogError(err, "Failed to get required parameters")
c.JSON(http.StatusOK, EventingTestFileWebhookResponse{
Status: "error",
Error: err.Error(),
})
return
}
// parse the file
eventData, err := eventing.Ingest([]byte(input.Input.FileContents))
if err != nil {
logging.LogError(err, "Failed to process file contents as Event data")
c.JSON(http.StatusOK, EventingTestFileWebhookResponse{
Status: "error",
Error: err.Error(),
})
return
}
err = eventing.EnsureActions(&eventData)
if err != nil {
logging.LogError(err, "Failed to ensure actions are correct")
c.JSON(http.StatusOK, EventingTestFileWebhookResponse{
Status: "error",
Error: err.Error(),
})
return
}
err = eventing.EnsureTrigger(&eventData)
if err != nil {
logging.LogError(err, "Failed to ensure triggers are correct")
c.JSON(http.StatusOK, EventingTestFileWebhookResponse{
Status: "error",
Error: err.Error(),
})
return
}
err = eventing.ResolveDependencies(&eventData)
if err != nil {
logging.LogError(err, "Failed to resolve dependencies")
c.JSON(http.StatusOK, EventingTestFileWebhookResponse{
Status: "error",
Error: err.Error(),
})
return
}
c.JSON(http.StatusOK, EventingTestFileWebhookResponse{
Status: "success",
})
}
@@ -282,6 +282,7 @@ func setRoutes(r *gin.Engine) {
noSpectators.POST("eventing_trigger_retry_from_step_webhook", webcontroller.EventingTriggerRetryFromStepWebhook)
noSpectators.POST("eventing_trigger_runagain_webhook", webcontroller.EventingTriggerRunAgainWebhook)
noSpectators.POST("eventing_trigger_update_webhook", webcontroller.EventingTriggerUpdateWebhook)
noSpectators.POST("eventing_test_file_webhook", webcontroller.EventingTestFileWebhook)
// keylogs
noSpectators.POST("keylog_create_webhook", webcontroller.CreateKeylogWebhook)
}
@@ -1,5 +1,5 @@
server {
listen 0.0.0.0:${MYTHIC_REACT_PORT};
listen ${MYTHIC_REACT_BIND_IPV4}:${MYTHIC_REACT_PORT};
client_max_body_size 500M; # allows file uploads up to 500 megabytes
ssl_session_timeout 1d;
root /mythic;
@@ -1,15 +1,15 @@
{
"files": {
"main.css": "/new/static/css/main.7e143bf2.css",
"main.js": "/new/static/js/main.03d10eb9.js",
"main.js": "/new/static/js/main.32bd58e5.js",
"static/media/mythic-red.png": "/new/static/media/mythic-red.203468a4e5240d239aa0.png",
"static/media/mythic_red_small.svg": "/new/static/media/mythic_red_small.793b41cc7135cdede246661ec232976b.svg",
"index.html": "/new/index.html",
"main.7e143bf2.css.map": "/new/static/css/main.7e143bf2.css.map",
"main.03d10eb9.js.map": "/new/static/js/main.03d10eb9.js.map"
"main.32bd58e5.js.map": "/new/static/js/main.32bd58e5.js.map"
},
"entrypoints": [
"static/css/main.7e143bf2.css",
"static/js/main.03d10eb9.js"
"static/js/main.32bd58e5.js"
]
}
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.03d10eb9.js"></script><link href="/new/static/css/main.7e143bf2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.32bd58e5.js"></script><link href="/new/static/css/main.7e143bf2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -36,17 +36,6 @@
* Released under the MIT license
*/
/**
* match-sorter-utils
*
* Copyright (c) TanStack
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* @license React
* react-dom.production.min.js
@@ -117,13 +106,6 @@
* LICENSE file in the root directory of this source tree.
*/
/**
* @name match-sorter
* @license MIT license.
* @copyright (c) 2099 Kent C. Dodds
* @author Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)
*/
/**
* @remix-run/router v1.16.1
*
@@ -1,6 +1,6 @@
server {
${NGINX_USE_IPV4} listen 0.0.0.0:${NGINX_PORT} ${NGINX_USE_SSL};
${NGINX_USE_IPV6} listen [::]:${NGINX_PORT} ${NGINX_USE_SSL};
${NGINX_USE_IPV4} listen ${NGINX_BIND_IPV4}:${NGINX_PORT} ${NGINX_USE_SSL};
${NGINX_USE_IPV6} listen ${NGINX_BIND_IPV6}:${NGINX_PORT} ${NGINX_USE_SSL};
ssl_certificate /etc/ssl/private/mythic-cert.crt;
ssl_certificate_key /etc/ssl/private/mythic-ssl.key;
client_max_body_size 500M; # allows file uploads up to 500 megabytes