[release] v0.12.0-unstable33

This commit is contained in:
Yann Stepienik 2023-11-02 16:52:27 +00:00
parent 6a5f9b74fe
commit 5b8622ff57
17 changed files with 394 additions and 103 deletions

View file

@ -6,7 +6,8 @@
- New color slider with reset buttons
- Fixed blinking modals issues
- Added lazyloading to URL and Servapp pages images
- Added a button in the config page to easily download the docker backup
- Added a dangerous IP detector that stops sending HTTP response to IPs that are abusing various shields features
- Added a button in the servapp page to easily download the docker backup
- Improve display or icons [fixes #121]
- Refactored Mongo connection code [fixes #111]
- Forward simultaneously TCP and UDP [fixes #122]

View file

@ -71,6 +71,15 @@ const NavItem = ({ item, level }) => {
display: 'inline-block',
}}>Beta</span></>;
if(item.title === "Monitoring")
item.title = <>{item.title} <span style={{
color: 'gray',
fontSize: '11px',
textDecoration: 'italic',
transform: 'translateY(-5px)',
display: 'inline-block',
}}>New!</span></>;
return (
<ListItemButton
{...listItemProps}

View file

@ -6,11 +6,12 @@ import HostChip from '../../../components/hostChip';
import { RouteMode, RouteSecurity } from '../../../components/routeComponents';
import { getFaviconURL } from '../../../utils/routes';
import * as API from '../../../api';
import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
import { CheckOutlined, ClockCircleOutlined, ContainerOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, InfoCircleFilled, InfoCircleOutlined, LockOutlined, NodeExpandOutlined, SafetyCertificateOutlined, UpOutlined } from "@ant-design/icons";
import IsLoggedIn from '../../../isLoggedIn';
import { redirectToLocal } from '../../../utils/indexs';
import { CosmosCheckbox } from '../users/formShortcuts';
import { Field } from 'formik';
import MiniPlotComponent from '../../dashboard/components/mini-plot';
const info = {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
@ -40,15 +41,32 @@ const RouteOverview = ({ routeConfig }) => {
<div>
<img className="loading-image" alt="" src={getFaviconURL(routeConfig)} width="128px" />
</div>
<Stack spacing={2} >
<strong>Description</strong>
<Stack spacing={2} style={{ width: '100%' }}>
<strong><ContainerOutlined />Description</strong>
<div style={info}>{routeConfig.Description}</div>
<strong>URL</strong>
<strong><NodeExpandOutlined /> URL</strong>
<div><HostChip route={routeConfig} /></div>
<strong>Target</strong>
<strong><InfoCircleOutlined /> Target</strong>
<div><RouteMode route={routeConfig} /> <Chip label={routeConfig.Target} /></div>
<strong>Security</strong>
<strong><SafetyCertificateOutlined/> Security</strong>
<div><RouteSecurity route={routeConfig} /></div>
<strong><DashboardOutlined/> Monitoring</strong>
<div>
<MiniPlotComponent metrics={[
"cosmos.proxy.route.success." + routeConfig.Name,
"cosmos.proxy.route.error." + routeConfig.Name,
]} labels={{
["cosmos.proxy.route.error." + routeConfig.Name]: "Error",
["cosmos.proxy.route.success." + routeConfig.Name]: "Succ."
}}/>
<MiniPlotComponent metrics={[
"cosmos.proxy.route.bytes." + routeConfig.Name,
"cosmos.proxy.route.time." + routeConfig.Name,
]} labels={{
["cosmos.proxy.route.bytes." + routeConfig.Name]: "Bytes",
["cosmos.proxy.route.time." + routeConfig.Name]: "Time"
}}/>
</div>
</Stack>
</Stack>
</MainCard>

View file

@ -40,6 +40,7 @@ import {RouteActions, RouteMode, RouteSecurity} from '../../../components/routeC
import { useNavigate } from 'react-router';
import NewRouteCreate from '../routes/newRoute';
import LazyLoad from 'react-lazyload';
import MiniPlotComponent from '../../dashboard/components/mini-plot';
const stickyButton = {
position: 'fixed',
@ -173,6 +174,14 @@ const ProxyManagement = () => {
<div style={{display:'inline-block', textDecoration: 'inherit', fontSize: '90%', opacity: '90%'}}>{r.Description}</div>
</>
},
{ title: 'Network', screenMin: 'lg', clickable:false, field: (r) =>
<div style={{width: '450px', marginLeft: '-60px', marginBottom: '10px'}}>
<MiniPlotComponent metrics={[
"cosmos.proxy.route.bytes." + r.Name,
"cosmos.proxy.route.time." + r.Name,
]} noLabels noBackground/>
</div>
},
{ title: 'Origin', screenMin: 'md', clickable:true, search: (r) => r.Host + ' ' + r.PathPrefix, field: (r) => <HostChip route={r} /> },
{ title: 'Target', screenMin: 'md', search: (r) => r.Target, field: (r) => <><RouteMode route={r} /> <Chip label={r.Target} /></> },
{ title: 'Security', screenMin: 'lg', field: (r) => <RouteSecurity route={r} />,

View file

@ -30,7 +30,7 @@ import { FormaterForMetric, formatDate, toUTC } from './utils';
import * as API from '../../../api';
const _MiniPlotComponent = ({metrics, labels}) => {
const _MiniPlotComponent = ({metrics, labels, noLabels, noBackground}) => {
const theme = useTheme();
const { primary, secondary } = theme.palette.text;
const [dataMetrics, setDataMetrics] = useState([]);
@ -131,7 +131,7 @@ const _MiniPlotComponent = ({metrics, labels}) => {
show: false
}
},
yaxis: dataMetrics.map((data) => ({
yaxis: dataMetrics.length ? dataMetrics.map((data) => ({
labels: {
show: false
},
@ -141,7 +141,17 @@ const _MiniPlotComponent = ({metrics, labels}) => {
axisTicks: {
show: false
}
})),
})) : {
labels: {
show: false
},
axisBorder: {
show: false
},
axisTicks: {
show: false
}
},
tooltip: {
enabled: false
},
@ -160,18 +170,18 @@ const _MiniPlotComponent = ({metrics, labels}) => {
const formaters = dataMetrics.map((data) => FormaterForMetric(data));
return <Stack direction='row' spacing={3}
alignItems='center' sx={{padding: '0px 20px', width: '100%', backgroundColor: 'rgba(0,0,0,0.1)'}}
alignItems='center' sx={{padding: '0px 20px', width: '100%', backgroundColor: noBackground ? '' : 'rgba(0,0,0,0.1)'}}
justifyContent={'space-around'}>
{dataMetrics && dataMetrics.map((dataMetric, di) =>
{!noLabels && dataMetrics && dataMetrics.map((dataMetric, di) =>
<Stack direction='column' justifyContent={'center'} alignItems={'center'} spacing={0} style={{
width: '60px',
}}>
{dataMetric.Values.length && <div style={{
{dataMetric.Values.length ? <div style={{
fontWeight: 'bold',
fontSize: '110%',
whiteSpace: 'nowrap',
}}>{formaters[di](dataMetric.Values[dataMetric.Values.length - 1].Value)}</div>
}}>{formaters[di](dataMetric.Values[dataMetric.Values.length - 1].Value)}</div> : formaters[di](0)
}
<div>
<div style={{
@ -195,8 +205,8 @@ const _MiniPlotComponent = ({metrics, labels}) => {
</Stack>
}
const MiniPlotComponent = ({ metrics, labels }) => {
const memoizedComponent = useMemo(() => <_MiniPlotComponent metrics={metrics} labels={labels} />, [metrics]);
const MiniPlotComponent = ({ metrics, labels, noLabels, noBackground }) => {
const memoizedComponent = useMemo(() => <_MiniPlotComponent noBackground={noBackground} noLabels={noLabels} metrics={metrics} labels={labels} />, [metrics]);
return memoizedComponent;
};

View file

@ -95,6 +95,7 @@ const PlotComponent = ({ title, slot, data, SimpleDesign, withSelector, xAxis, z
theme.palette.primary.main.replace('rgb(', 'rgba('),
theme.palette.secondary.main.replace('rgb(', 'rgba('),
theme.palette.error.main.replace('rgb(', 'rgba('),
theme.palette.warning.main.replace('rgb(', 'rgba('),
],
xaxis: {
categories:
@ -115,7 +116,7 @@ const PlotComponent = ({ title, slot, data, SimpleDesign, withSelector, xAxis, z
max: zoom.xaxis && zoom.xaxis.max,
},
yaxis: toProcess.map((thisdata, ida) => ({
opposite: ida === 0,
opposite: ida % 2 === 1,
labels: {
style: {
colors: [secondary],

View file

@ -56,10 +56,18 @@ function formatDate(now, time) {
}
function descendingComparator(a, b, orderBy) {
if (parseFloat(b[orderBy]) < parseFloat(a[orderBy])) {
let a1 = a[orderBy];
let b1 = b[orderBy];
if(orderBy != 'name') {
a1 = parseFloat(a["__" + orderBy]);
b1 = parseFloat(b["__" + orderBy]);
}
if (b1 < a1) {
return -1;
}
if (parseFloat(b[orderBy]) > parseFloat(a[orderBy])) {
if (b1 > a1) {
return 1;
}
return 0;
@ -162,7 +170,8 @@ const TableComponent = ({ title, data, displayMax, render, xAxis, slot, zoom}) =
return 0;
}
} else {
return item.Values[date] ? item.Values[date].Value : 0;
let realIndex = item.Values.length - 1 - date
return item.Values[realIndex] ? item.Values[realIndex].Value : 0;
}
})
.reduce((a, b) => {
@ -304,7 +313,7 @@ const TableComponent = ({ title, data, displayMax, render, xAxis, slot, zoom}) =
>
<OrderTableHead headCells={headCells} order={order} orderBy={orderBy} setOrderBy={setOrderBy} setOrder={setOrder} />
<TableBody style={{height:'409px', overflow: 'auto'}}>
{stableSort(rows, getComparator(order, "__" + orderBy)).map((row, index) => {
{stableSort(rows, getComparator(order, orderBy)).map((row, index) => {
const isItemSelected = false // isSelected(row.trackingNo);
const labelId = `enhanced-table-checkbox-${index}`;

View file

@ -1,5 +1,5 @@
export const simplifyNumber = (num, unit) => {
if(!num) return 0;
if(typeof num == 'undefined' || num === null) return 0;
num = Math.round(num * 100) / 100;

View file

@ -1,10 +1,12 @@
import {
Grid,
LinearProgress,
Tooltip,
} from '@mui/material';
import PlotComponent from './components/plot';
import TableComponent from './components/table';
import { InfoCircleOutlined } from '@ant-design/icons';
const ProxyDashboard = ({ xAxis, zoom, setZoom, slot, metrics }) => {
console.log(metrics)
@ -25,7 +27,6 @@ const ProxyDashboard = ({ xAxis, zoom, setZoom, slot, metrics }) => {
]} />
</Grid>
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Requests Per URLs" data={
Object.keys(metrics).filter((key) => key.startsWith("cosmos.proxy.route.")).map((key) => metrics[key])
} />
@ -37,7 +38,17 @@ const ProxyDashboard = ({ xAxis, zoom, setZoom, slot, metrics }) => {
</Grid>
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Reasons For Blocked Requests" data={
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title={
<span>
Reasons For Blocked Requests <Tooltip title={<div>
<div><strong>bots</strong>: Bots</div>
<div><strong>geo</strong>: By Geolocation (blocked countries)</div>
<div><strong>referer</strong>: By Referer</div>
<div><strong>hostname</strong>: By Hostname (usually IP scanning threat)</div>
<div><strong>ip-whitelists</strong>: By IP Whitelists (Including restricted to Constellation)</div>
<div><strong>smart-shield</strong>: Smart Shield (various abuse metrics such as time, size, brute-force, concurrent requests, etc...). It does not include blocking for banned IP to save resources in case of potential attacks</div>
</div>}><InfoCircleOutlined /></Tooltip>
</span>} data={
Object.keys(metrics).filter((key) => key.startsWith("cosmos.proxy.blocked.")).map((key) => metrics[key])
} />
</Grid>

View file

@ -14,7 +14,7 @@ const ResourceDashboard = ({ xAxis, zoom, setZoom, slot, metrics }) => {
<PlotComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title={'Resources'} data={[metrics["cosmos.system.cpu.0"], metrics["cosmos.system.ram"]]} />
</Grid>
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Containers - Resources" data={
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Containers - Average Resources" data={
Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.docker.cpu") || key.startsWith("cosmos.system.docker.ram")).map((key) => metrics[key])
} />
@ -22,7 +22,7 @@ const ResourceDashboard = ({ xAxis, zoom, setZoom, slot, metrics }) => {
<PlotComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title={'Network'} data={[metrics["cosmos.system.netTx"], metrics["cosmos.system.netRx"]]} />
</Grid>
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Containers - Network" data={
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Containers - Average Network" data={
Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.docker.net")).map((key) => metrics[key])
} />

View file

@ -13,6 +13,7 @@ import IsLoggedIn from "../../isLoggedIn";
import { ServAppIcon } from "../../utils/servapp-icon";
import Chart from 'react-apexcharts';
import { useClientInfos } from "../../utils/hooks";
import { FormaterForMetric, formatDate } from "../dashboard/components/utils";
export const HomeBackground = () => {
@ -90,6 +91,7 @@ const HomePage = () => {
const isMd = useMediaQuery(theme.breakpoints.up('md'));
const {role} = useClientInfos();
const isAdmin = role === "2";
const [metrics, setMetrics] = useState(null);
const blockStyle = {
margin: 0,
@ -114,6 +116,43 @@ const HomePage = () => {
});
}
function getMetrics() {
API.metrics.get([
"cosmos.system.cpu.0",
"cosmos.system.ram",
"cosmos.system.netTx",
"cosmos.system.netRx",
"cosmos.proxy.all.success",
"cosmos.proxy.all.error",
]).then((res) => {
setMetrics(prevMetrics => {
let finalMetrics = prevMetrics ? { ...prevMetrics } : {};
if(res.data) {
res.data.forEach((metric) => {
finalMetrics[metric.Key] = metric;
});
return finalMetrics;
}
});
});
}
useEffect(() => {
refreshStatus();
let interval = setInterval(() => {
if(isMd)
getMetrics();
}, 10000);
if(isMd) getMetrics();
return () => {
clearInterval(interval);
};
}, []);
const refreshConfig = () => {
if(isAdmin) {
API.docker.list().then((res) => {
@ -185,11 +224,11 @@ const HomePage = () => {
},
value: {
formatter: function (val) {
return val + "%";
return val + "%"
},
color: "#111",
offsetY: 9,
fontSize: "24px",
offsetY: 7,
fontSize: "18px",
show: true
}
}
@ -214,6 +253,39 @@ const HomePage = () => {
labels: []
};
const bigNb = {
fontSize: '24px',
fontWeight: "bold",
textAlign: "center",
color: isDark ? "white" : "black",
textShadow: "0px 0px 5px #000",
lineHeight: "97px",
}
let latestCPU = metrics && metrics["cosmos.system.cpu.0"] && metrics["cosmos.system.cpu.0"].Values[metrics["cosmos.system.cpu.0"].Values.length - 1].Value;
let formatRAM = metrics && FormaterForMetric(metrics["cosmos.system.ram"], false);
let latestRAMRaw = metrics && metrics["cosmos.system.ram"] && metrics["cosmos.system.ram"].Values[metrics["cosmos.system.ram"].Values.length - 1].Value;
let latestRAM = metrics && metrics["cosmos.system.ram"] && formatRAM(metrics["cosmos.system.ram"].Values[metrics["cosmos.system.ram"].Values.length - 1].Value);
let maxRAM = metrics && metrics["cosmos.system.ram"] && formatRAM(metrics["cosmos.system.ram"].Max);
let maxRAMRaw = metrics && metrics["cosmos.system.ram"] && metrics["cosmos.system.ram"].Max;
let now = new Date();
now = "day_" + formatDate(now);
let formatNetwork = metrics && FormaterForMetric(metrics["cosmos.system.netTx"], false);
let latestNetworkRawTx = (metrics && metrics["cosmos.system.netTx"] && metrics["cosmos.system.netTx"].ValuesAggl[now].Value) || 0;
let latestNetworkTx = metrics && formatNetwork(latestNetworkRawTx);
let latestNetworkRawRx = (metrics && metrics["cosmos.system.netRx"] && metrics["cosmos.system.netRx"].ValuesAggl[now].Value) || 0;
let latestNetworkRx = metrics && formatNetwork(latestNetworkRawRx);
let latestNetworkSum = metrics && formatNetwork(latestNetworkRawTx + latestNetworkRawRx);
let formatRequests = metrics && FormaterForMetric(metrics["cosmos.proxy.all.success"], false);
let latestRequestsRaw = (metrics && metrics["cosmos.proxy.all.success"] && metrics["cosmos.proxy.all.success"].ValuesAggl[now].Value) || 0;
let latestRequests = metrics && formatRequests(latestRequestsRaw);
let latestRequestsErrorRaw = (metrics && metrics["cosmos.proxy.all.error"] && metrics["cosmos.proxy.all.error"].ValuesAggl[now].Value) || 0;
let latestRequestsError = metrics && formatRequests(latestRequestsErrorRaw);
let latestRequestSum = metrics && formatRequests(latestRequestsRaw + latestRequestsErrorRaw);
return <Stack spacing={2} >
<IsLoggedIn />
@ -276,72 +348,139 @@ const HomePage = () => {
</Stack>
<Grid2 container spacing={2} style={{ zIndex: 2 }}>
{/* {(!isMd || !coStatus || !coStatus.resources || !coStatus.resources.cpu ) && (<>
<Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={'000'}>
<Box className='app' style={{ padding: '20px', height: "107px", borderRadius: 5, ...appColor }}>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>CPU</div>
<div>--</div>
<div>--</div>
</Stack>
</Box>
</Grid2>
<Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={'001'}>
<Box className='app' style={{ padding: '20px', height: "107px", borderRadius: 5, ...appColor }}>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>RAM</div>
<div>Available: --GB</div>
<div>Used: --GB</div>
</Stack>
</Box>
</Grid2>
</>)}
{isMd && coStatus && coStatus.resources.cpu && (<>
<Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={'000'}>
<Box className='app' style={{height: '106px', borderRadius: 5, ...appColor }}>
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
<div style={{width: '110px', height: '97px'}}>
<Chart
options={optionsRadial}
series={[parseInt(coStatus.resources.cpu)]}
type="radialBar"
height={120}
width={120}
/>
</div>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>CPU</div>
<div>{coStatus.CPU}</div>
<div>{coStatus.AVX ? "AVX Supported" : "No AVX Support"}</div>
</Stack>
</Stack>
</Box>
</Grid2>
<Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={'001'}>
<Box className='app' style={{height: '106px',borderRadius: 5, ...appColor }}>
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
<div style={{width: '110px', height: '97px'}}>
<Chart
options={optionsRadial}
series={[parseInt(
coStatus.resources.ram / (coStatus.resources.ram + coStatus.resources.ramFree) * 100
)]}
type="radialBar"
height={120}
width={120}
/>
</div>
<Stack style={{flexGrow: 1}} spacing={0}>
{coStatus && !coStatus.MonitoringDisabled && (<>
{isMd && !metrics && (<>
<Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'000'}>
<Box className='app' style={{ padding: '20px', height: "107px", borderRadius: 5, ...appColor }}>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>CPU</div>
<div>--</div>
<div>--</div>
</Stack>
</Box>
</Grid2>
<Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'001'}>
<Box className='app' style={{ padding: '20px', height: "107px", borderRadius: 5, ...appColor }}>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>RAM</div>
<div>Available: {Math.ceil((coStatus.resources.ram + coStatus.resources.ramFree) / 1024 / 1024 / 1024)}GB</div>
<div>Used: {Math.ceil(coStatus.resources.ram / 1024 / 1024 / 1024)}GB</div>
<div>Available: -- GB</div>
<div>Used: -- GB</div>
</Stack>
</Box>
</Grid2>
<Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'001'}>
<Box className='app' style={{height: '106px',borderRadius: 5, ...appColor }}>
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
<div style={{width: '110px', height: '97px'}}>
<div style={bigNb}>
-
</div>
</div>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>Network</div>
<div>Tx: -</div>
<div>Rx: -</div>
</Stack>
</Stack>
</Stack>
</Box>
</Grid2>
</Box>
</Grid2>
<Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'001'}>
<Box className='app' style={{height: '106px',borderRadius: 5, ...appColor }}>
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
<div style={{width: '110px', height: '97px'}}>
<div style={bigNb}>
-
</div>
</div>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>Requests</div>
<div>Success: -</div>
<div>Error: -</div>
</Stack>
</Stack>
</Box>
</Grid2>
</>)}
</>)} */}
{isMd && metrics && (<>
<Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'000'}>
<Box className='app' style={{height: '106px', borderRadius: 5, ...appColor }}>
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
<div style={{width: '110px', height: '97px'}}>
<Chart
options={optionsRadial}
series={[latestCPU]}
type="radialBar"
height={120}
width={120}
/>
</div>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>CPU</div>
<div>{coStatus.CPU}</div>
<div>{coStatus.AVX ? "AVX Supported" : "No AVX Support"}</div>
</Stack>
</Stack>
</Box>
</Grid2>
<Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'001'}>
<Box className='app' style={{height: '106px',borderRadius: 5, ...appColor }}>
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
<div style={{width: '110px', height: '97px'}}>
<Chart
options={optionsRadial}
// series={[parseInt(
// coStatus.resources.ram / (coStatus.resources.ram + coStatus.resources.ramFree) * 100
// )]}
series={[parseInt(latestRAMRaw / maxRAMRaw * 100)]}
type="radialBar"
height={120}
width={120}
/>
</div>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>RAM</div>
<div>Available: {maxRAM}</div>
<div>Used: {latestRAM}</div>
</Stack>
</Stack>
</Box>
</Grid2>
<Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'001'}>
<Box className='app' style={{height: '106px',borderRadius: 5, ...appColor }}>
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
<div style={{width: '110px', height: '97px'}}>
<div style={bigNb}>
{latestNetworkSum}
</div>
</div>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>Network</div>
<div>Tx: {latestNetworkTx}</div>
<div>Rx: {latestNetworkRx}</div>
</Stack>
</Stack>
</Box>
</Grid2>
<Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'001'}>
<Box className='app' style={{height: '106px',borderRadius: 5, ...appColor }}>
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
<div style={{width: '110px', height: '97px'}}>
<div style={bigNb}>
{latestRequestSum}
</div>
</div>
<Stack style={{flexGrow: 1}} spacing={0}>
<div style={{fontSize: '18px', fontWeight: "bold"}}>Requests</div>
<div>Success: {latestRequests}</div>
<div>Error: {latestRequestsError}</div>
</Stack>
</Stack>
</Box>
</Grid2>
</>)}
</>)}
{config && servApps && routes.map((route) => {
let skip = route.Mode == "REDIRECT";

View file

@ -1,6 +1,6 @@
{
"name": "cosmos-server",
"version": "0.12.0-unstable32",
"version": "0.12.0-unstable33",
"description": "",
"main": "test-server.js",
"bugs": {

View file

@ -275,7 +275,8 @@ func InitServer() *mux.Router {
utils.Log("Initialising HTTP(S) Router and all routes")
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/logo", SendLogo)
router.Use(utils.BlockBannedIPs)
router.Use(middleware.Logger)
@ -283,6 +284,8 @@ func InitServer() *mux.Router {
router.Use(utils.BlockByCountryMiddleware(config.BlockedCountries, config.CountryBlacklistIsWhitelist))
}
router.HandleFunc("/logo", SendLogo)
srapi := router.PathPrefix("/cosmos").Subrouter()
srapi.HandleFunc("/api/dns", GetDNSRoute)

View file

@ -87,6 +87,14 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination htt
destination = utils.Restrictions(route.RestrictToConstellation, route.WhitelistInboundIPs)(destination)
if route.BlockCommonBots {
destination = BotDetectionMiddleware(destination)
}
if route.BlockAPIAbuse {
destination = utils.BlockPostWithoutReferer(destination)
}
destination = SmartShieldMiddleware(route.Name, route)(destination)
originCORS := route.CORSOrigin
@ -133,14 +141,6 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination htt
destination = utils.BandwithLimiterMiddleware(route.MaxBandwith)(destination)
}
if route.BlockCommonBots {
destination = BotDetectionMiddleware(destination)
}
if route.BlockAPIAbuse {
destination = utils.BlockPostWithoutReferer(destination)
}
if !route.DisableHeaderHardening {
destination = utils.SetSecurityHeaders(destination)
}

View file

@ -165,6 +165,7 @@ func (shield *smartShieldState) isAllowedToReqest(shieldID string, policy utils.
banType: PERM,
time: time.Now(),
})
utils.Warn("User " + ClientID + " has been banned permanently: "+ fmt.Sprintf("%+v", userConsumed))
return false
} else if nbStrikes >= 3 {
@ -355,6 +356,7 @@ func SmartShieldMiddleware(shieldID string, route utils.ProxyRouteConfig) func(h
if !isPrivileged(r, policy) && !shield.isAllowedToReqest(shieldID, policy, userConsumed) {
lastBan := shield.GetLastBan(policy, userConsumed)
go metrics.PushShieldMetrics("smart-shield")
utils.IncrementIPAbuseCounter(clientID)
utils.Log("SmartShield: User is blocked due to abuse: " + fmt.Sprintf("%+v", lastBan))
http.Error(w, "Too many requests", http.StatusTooManyRequests)
return

View file

@ -63,6 +63,7 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) {
"CPU": runtime.GOARCH,
"AVX": cpu.X86.HasAVX,
"LetsEncryptErrors": utils.LetsEncryptErrors,
"MonitoringDisabled": utils.GetMainConfig().MonitoringDisabled,
},
})
} else {

View file

@ -7,6 +7,8 @@ import (
"net"
"strings"
"fmt"
"sync"
"sync/atomic"
"github.com/mxk/go-flowrate/flowrate"
"github.com/oschwald/geoip2-golang"
@ -16,6 +18,66 @@ import (
var PushShieldMetrics func(string)
type safeInt struct {
val int64
}
var BannedIPs = sync.Map{}
// Close connection right away if banned (save resources)
func IncrementIPAbuseCounter(ip string) {
// Load or store a new *safeInt
actual, _ := BannedIPs.LoadOrStore(ip, &safeInt{})
counter := actual.(*safeInt)
// Increment the counter using atomic for concurrent access
atomic.AddInt64(&counter.val, 1)
}
func getIPAbuseCounter(ip string) int64 {
// Load the *safeInt
actual, ok := BannedIPs.Load(ip)
if !ok {
return 0
}
counter := actual.(*safeInt)
// Load the value using atomic for concurrent access
return atomic.LoadInt64(&counter.val)
}
func BlockBannedIPs(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
if hj, ok := w.(http.Hijacker); ok {
conn, _, err := hj.Hijack()
if err == nil {
conn.Close()
}
}
return
}
nbAbuse := getIPAbuseCounter(ip)
// Debug("IP " + ip + " has " + fmt.Sprintf("%d", nbAbuse) + " abuse(s)")
if nbAbuse > 1000 {
if hj, ok := w.(http.Hijacker); ok {
conn, _, err := hj.Hijack()
if err == nil {
conn.Close()
}
}
return
}
next.ServeHTTP(w, r)
})
}
func MiddlewareTimeout(timeout time.Duration) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
@ -151,6 +213,7 @@ func BlockByCountryMiddleware(blockedCountries []string, CountryBlacklistIsWhite
if blocked {
PushShieldMetrics("geo")
IncrementIPAbuseCounter(ip)
http.Error(w, "Access denied", http.StatusForbidden)
return
}
@ -183,6 +246,12 @@ func BlockPostWithoutReferer(next http.Handler) http.Handler {
PushShieldMetrics("referer")
Error("Blocked POST request without Referer header", nil)
http.Error(w, "Bad Request: Invalid request.", http.StatusBadRequest)
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
if ip != "" {
IncrementIPAbuseCounter(ip)
}
return
}
}
@ -221,6 +290,12 @@ func EnsureHostname(next http.Handler) http.Handler {
Error("Invalid Hostname " + r.Host + " for request. Expecting one of " + fmt.Sprintf("%v", hostnames), nil)
w.WriteHeader(http.StatusBadRequest)
http.Error(w, "Bad Request: Invalid hostname. Use your domain instead of your IP to access your server. Check logs if more details are needed.", http.StatusBadRequest)
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
if ip != "" {
IncrementIPAbuseCounter(ip)
}
return
}
@ -312,12 +387,14 @@ func Restrictions(RestrictToConstellation bool, WhitelistInboundIPs []string) fu
if(!isInConstellation) {
if(!isUsingWhiteList) {
PushShieldMetrics("ip-whitelists")
IncrementIPAbuseCounter(ip)
Error("Request from " + ip + " is blocked because of restrictions", nil)
Debug("Blocked by RestrictToConstellation isInConstellation isUsingWhiteList")
http.Error(w, "Access denied", http.StatusForbidden)
return
} else if (!isInWhitelist) {
PushShieldMetrics("ip-whitelists")
IncrementIPAbuseCounter(ip)
Error("Request from " + ip + " is blocked because of restrictions", nil)
Debug("Blocked by RestrictToConstellation isInConstellation isInWhitelist")
http.Error(w, "Access denied", http.StatusForbidden)
@ -326,6 +403,7 @@ func Restrictions(RestrictToConstellation bool, WhitelistInboundIPs []string) fu
}
} else if(isUsingWhiteList && !isInWhitelist) {
PushShieldMetrics("ip-whitelists")
IncrementIPAbuseCounter(ip)
Error("Request from " + ip + " is blocked because of restrictions", nil)
Debug("Blocked by RestrictToConstellation isInConstellation isUsingWhiteList isInWhitelist")
http.Error(w, "Access denied", http.StatusForbidden)