[release] v0.10.0-unstable15
This commit is contained in:
parent
ef37940742
commit
a6b96bc42a
|
@ -6,7 +6,11 @@
|
||||||
## Version 0.10.0
|
## Version 0.10.0
|
||||||
- Added Constellation
|
- Added Constellation
|
||||||
- DNS Challenge is now used for all certificates when enabled
|
- DNS Challenge is now used for all certificates when enabled
|
||||||
>>>>>>> b8a9e71 ([release] v0.10.0-unstable)
|
- Rework headers for better compatibility
|
||||||
|
|
||||||
|
## Version 0.9.20 - 0.9.21
|
||||||
|
- Add option to disable CORS hardening (with empty value)
|
||||||
|
|
||||||
## Version 0.9.19
|
## Version 0.9.19
|
||||||
- Add country whitelist option to geoblocker
|
- Add country whitelist option to geoblocker
|
||||||
- No countries blocked by default anymore
|
- No countries blocked by default anymore
|
||||||
|
|
|
@ -28,6 +28,16 @@ function restart() {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
return wrap(fetch('/cosmos/api/constellation/reset', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
function getConfig() {
|
function getConfig() {
|
||||||
return wrap(fetch('/cosmos/api/constellation/config', {
|
return wrap(fetch('/cosmos/api/constellation/config', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
@ -46,10 +56,22 @@ function getLogs() {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function connect(file) {
|
||||||
|
return wrap(fetch('/cosmos/api/constellation/connect', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(file),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
list,
|
list,
|
||||||
addDevice,
|
addDevice,
|
||||||
restart,
|
restart,
|
||||||
getConfig,
|
getConfig,
|
||||||
getLogs,
|
getLogs,
|
||||||
|
reset,
|
||||||
|
connect,
|
||||||
};
|
};
|
48
client/src/components/confirmModal.jsx
Normal file
48
client/src/components/confirmModal.jsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// material-ui
|
||||||
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
import { Button } from '@mui/material';
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const ConfirmModal = ({ callback, label, content }) => {
|
||||||
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
|
||||||
|
<DialogTitle>Are you sure?</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
{content}
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => {
|
||||||
|
setOpenModal(false);
|
||||||
|
}}>Cancel</Button>
|
||||||
|
<LoadingButton
|
||||||
|
onClick={() => {
|
||||||
|
callback();
|
||||||
|
setOpenModal(false);
|
||||||
|
}}>Confirm</LoadingButton>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
disableElevation
|
||||||
|
variant="outlined"
|
||||||
|
color="warning"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmModal;
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { UploadOutlined } from '@ant-design/icons';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export default function UploadButtons({OnChange, accept, label}) {
|
export default function UploadButtons({OnChange, accept, label, variant, fullWidth, size}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
|
@ -14,7 +14,8 @@ export default function UploadButtons({OnChange, accept, label}) {
|
||||||
onChange={OnChange}
|
onChange={OnChange}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="contained-button-file">
|
<label htmlFor="contained-button-file">
|
||||||
<Button variant="contained" component="span" startIcon={<UploadOutlined />}>
|
<Button variant={variant || "contained"} component="span"
|
||||||
|
fullWidth={fullWidth} startIcon={<UploadOutlined />}>
|
||||||
{label || 'Upload'}
|
{label || 'Upload'}
|
||||||
</Button>
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -12,10 +12,67 @@ import { PlusCircleFilled } from '@ant-design/icons';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import * as API from '../../api';
|
import * as API from '../../api';
|
||||||
import { CosmosFormDivider, CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
|
import { CosmosCheckbox, CosmosFormDivider, CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
|
||||||
import { DownloadFile } from '../../api/downloadButton';
|
import { DownloadFile } from '../../api/downloadButton';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
|
|
||||||
|
const getDocker = (data, isCompose) => {
|
||||||
|
let lighthouses = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < data.LighthousesList.length; i++) {
|
||||||
|
const l = data.LighthousesList[i];
|
||||||
|
lighthouses += l.publicHostname + ";" + l.ip + ":" + l.port + ";" + l.isRelay + ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
let containerName = "cosmos-constellation-lighthouse";
|
||||||
|
let imageName = "cosmos-constellation-lighthouse:latest";
|
||||||
|
|
||||||
|
let volPath = "/var/lib/cosmos-constellation";
|
||||||
|
|
||||||
|
if (isCompose) {
|
||||||
|
return `
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
${containerName}:
|
||||||
|
image: ${imageName}
|
||||||
|
container_name: ${containerName}
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: bridge
|
||||||
|
ports:
|
||||||
|
- "${data.Port}:4242"
|
||||||
|
volumes:
|
||||||
|
- ${volPath}:/config
|
||||||
|
environment:
|
||||||
|
- CA=${JSON.stringify(data.CA)}
|
||||||
|
- CERT=${JSON.stringify(data.PrivateKey)}
|
||||||
|
- KEY=${JSON.stringify(data.PublicKey)}
|
||||||
|
- LIGHTHOUSES=${lighthouses}
|
||||||
|
- PUBLIC_HOSTNAME=${data.PublicHostname}
|
||||||
|
- IS_RELAY=${data.IsRelay}
|
||||||
|
- IP=${data.IP}
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return `
|
||||||
|
docker run -d \\
|
||||||
|
--name ${containerName} \\
|
||||||
|
--restart unless-stopped \\
|
||||||
|
--network bridge \\
|
||||||
|
-v ${volPath}:/config \\
|
||||||
|
-e CA=${JSON.stringify(data.CA)} \\
|
||||||
|
-e CERT=${JSON.stringify(data.PrivateKey)} \\
|
||||||
|
-e KEY=${JSON.stringify(data.PublicKey)} \\
|
||||||
|
-e LIGHTHOUSES=${lighthouses} \\
|
||||||
|
-e PUBLIC_HOSTNAME=${data.PublicHostname} \\
|
||||||
|
-e IS_RELAY=${data.IsRelay} \\
|
||||||
|
-e IP=${data.IP} \\
|
||||||
|
-p ${data.Port}:4242 \\
|
||||||
|
${imageName}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const [isDone, setIsDone] = useState(null);
|
const [isDone, setIsDone] = useState(null);
|
||||||
|
@ -63,12 +120,18 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
||||||
deviceName: '',
|
deviceName: '',
|
||||||
ip: firstIP,
|
ip: firstIP,
|
||||||
publicKey: '',
|
publicKey: '',
|
||||||
|
Port: "4242",
|
||||||
|
PublicHostname: '',
|
||||||
|
IsRelay: true,
|
||||||
|
isLighthouse: false,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
validationSchema={yup.object({
|
validationSchema={yup.object({
|
||||||
})}
|
})}
|
||||||
|
|
||||||
onSubmit={(values, { setSubmitting, setStatus, setErrors }) => {
|
onSubmit={(values, { setSubmitting, setStatus, setErrors }) => {
|
||||||
|
if(values.isLighthouse) values.nickname = null;
|
||||||
|
|
||||||
return API.constellation.addDevice(values).then(({data}) => {
|
return API.constellation.addDevice(values).then(({data}) => {
|
||||||
setIsDone(data);
|
setIsDone(data);
|
||||||
refreshConfig();
|
refreshConfig();
|
||||||
|
@ -85,52 +148,55 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
||||||
{isDone ? <DialogContent>
|
{isDone ? <DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
<p>
|
<p>
|
||||||
Device added successfully!
|
Device added successfully!
|
||||||
Download scan the QR Code from the Cosmos app or download the relevant
|
Download scan the QR Code from the Cosmos app or download the relevant
|
||||||
files to your device along side the config and network certificate to
|
files to your device along side the config and network certificate to
|
||||||
connect:
|
connect:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Stack spacing={2} direction={"column"}>
|
<Stack spacing={2} direction={"column"}>
|
||||||
<CosmosFormDivider title={"QR Code"} />
|
{/* {isDone.isLighthouse ? <>
|
||||||
<div style={{textAlign: 'center'}}>
|
<CosmosFormDivider title={"Docker"} />
|
||||||
<canvas style={{borderRadius: '15px'}} ref={canvasRef} />
|
<TextField
|
||||||
</div>
|
fullWidth
|
||||||
{/* <CosmosFormDivider title={"Cosmos Client (File)"} />
|
multiline
|
||||||
<DownloadFile
|
value={getDocker(isDone, false)}
|
||||||
filename={isDone.DeviceName + `.constellation`}
|
variant="outlined"
|
||||||
content={JSON.stringify(isDone, null, 2)}
|
size="small"
|
||||||
label={"Download " + isDone.DeviceName + `.constellation`}
|
disabled
|
||||||
/> */}
|
/>
|
||||||
|
<CosmosFormDivider title={"File (Docker-Compose)"} />
|
||||||
|
<DownloadFile
|
||||||
|
filename={`docker-compose.yml`}
|
||||||
|
content={getDocker(isDone, true)}
|
||||||
|
label={"Download docker-compose.yml"}
|
||||||
|
/>
|
||||||
|
</> : <> */}
|
||||||
|
<CosmosFormDivider title={"QR Code"} />
|
||||||
|
<div style={{textAlign: 'center'}}>
|
||||||
|
<canvas style={{borderRadius: '15px'}} ref={canvasRef} />
|
||||||
|
</div>
|
||||||
|
{/* </>} */}
|
||||||
|
|
||||||
<CosmosFormDivider title={"File"} />
|
<CosmosFormDivider title={"File"} />
|
||||||
|
|
||||||
<DownloadFile
|
<DownloadFile
|
||||||
filename={`constellation.yml`}
|
filename={`constellation.yml`}
|
||||||
content={isDone.Config}
|
content={isDone.Config}
|
||||||
label={"Download constellation.yml"}
|
label={"Download constellation.yml"}
|
||||||
/>
|
/>
|
||||||
{/* <DownloadFile
|
|
||||||
filename={isDone.DeviceName + `.key`}
|
|
||||||
content={isDone.PublicKey}
|
|
||||||
label={"Download " + isDone.DeviceName + `.key`}
|
|
||||||
/>
|
|
||||||
<DownloadFile
|
|
||||||
filename={isDone.DeviceName + `.crt`}
|
|
||||||
content={isDone.PrivateKey}
|
|
||||||
label={"Download " + isDone.DeviceName + `.crt`}
|
|
||||||
/>
|
|
||||||
<DownloadFile
|
|
||||||
filename={`ca.crt`}
|
|
||||||
content={isDone.CA}
|
|
||||||
label={"Download ca.crt"}
|
|
||||||
/> */}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</DialogContent> : <DialogContent>
|
</DialogContent> : <DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
<p>Add a device to the constellation using either the Cosmos or Nebula client</p>
|
<p>Add a Device to the constellation using either the Cosmos or Nebula client</p>
|
||||||
<div>
|
<div>
|
||||||
<Stack spacing={2} style={{}}>
|
<Stack spacing={2} style={{}}>
|
||||||
|
<CosmosCheckbox
|
||||||
|
name="isLighthouse"
|
||||||
|
label="Lighthouse"
|
||||||
|
formik={formik}
|
||||||
|
/>
|
||||||
|
{!formik.values.isLighthouse &&
|
||||||
<CosmosSelect
|
<CosmosSelect
|
||||||
name="nickname"
|
name="nickname"
|
||||||
label="Owner"
|
label="Owner"
|
||||||
|
@ -141,7 +207,7 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
||||||
return [u.nickname, u.nickname]
|
return [u.nickname, u.nickname]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>}
|
||||||
|
|
||||||
<CosmosInputText
|
<CosmosInputText
|
||||||
name="deviceName"
|
name="deviceName"
|
||||||
|
@ -155,12 +221,33 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
||||||
formik={formik}
|
formik={formik}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* <CosmosInputText
|
||||||
|
name="Port"
|
||||||
|
label="VPN Port (default: 4242)"
|
||||||
|
formik={formik}
|
||||||
|
/> */}
|
||||||
|
|
||||||
<CosmosInputText
|
<CosmosInputText
|
||||||
multiline
|
multiline
|
||||||
name="publicKey"
|
name="publicKey"
|
||||||
label="Public Key (Optional)"
|
label="Public Key (Optional)"
|
||||||
formik={formik}
|
formik={formik}
|
||||||
/>
|
/>
|
||||||
|
{formik.values.isLighthouse && <>
|
||||||
|
<CosmosFormDivider title={"Lighthouse Setup"} />
|
||||||
|
|
||||||
|
<CosmosInputText
|
||||||
|
name="PublicHostname"
|
||||||
|
label="Public Hostname"
|
||||||
|
formik={formik}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CosmosCheckbox
|
||||||
|
name="IsRelay"
|
||||||
|
label="Can Relay Traffic"
|
||||||
|
formik={formik}
|
||||||
|
/>
|
||||||
|
</>}
|
||||||
<div>
|
<div>
|
||||||
{formik.errors && formik.errors.length > 0 && <Stack spacing={2} direction={"column"}>
|
{formik.errors && formik.errors.length > 0 && <Stack spacing={2} direction={"column"}>
|
||||||
<Alert severity="error">{formik.errors.map((err) => {
|
<Alert severity="error">{formik.errors.map((err) => {
|
||||||
|
@ -189,7 +276,9 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
||||||
setIsDone(null);
|
setIsDone(null);
|
||||||
setOpenModal(true);
|
setOpenModal(true);
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant={
|
||||||
|
"contained"
|
||||||
|
}
|
||||||
startIcon={<PlusCircleFilled />}
|
startIcon={<PlusCircleFilled />}
|
||||||
>
|
>
|
||||||
Add Device
|
Add Device
|
||||||
|
|
|
@ -4,14 +4,26 @@ import * as API from "../../api";
|
||||||
import AddDeviceModal from "./addDevice";
|
import AddDeviceModal from "./addDevice";
|
||||||
import PrettyTableView from "../../components/tableView/prettyTableView";
|
import PrettyTableView from "../../components/tableView/prettyTableView";
|
||||||
import { DeleteButton } from "../../components/delete";
|
import { DeleteButton } from "../../components/delete";
|
||||||
import { CloudOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, TabletOutlined } from "@ant-design/icons";
|
import { CloudOutlined, CloudServerOutlined, CompassOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, TabletOutlined } from "@ant-design/icons";
|
||||||
import IsLoggedIn from "../../isLoggedIn";
|
import IsLoggedIn from "../../isLoggedIn";
|
||||||
import { Button, CircularProgress, Stack } from "@mui/material";
|
import { Alert, Button, CircularProgress, Stack } from "@mui/material";
|
||||||
import { CosmosCheckbox, CosmosFormDivider } from "../config/users/formShortcuts";
|
import { CosmosCheckbox, CosmosFormDivider, CosmosInputText } from "../config/users/formShortcuts";
|
||||||
import MainCard from "../../components/MainCard";
|
import MainCard from "../../components/MainCard";
|
||||||
import { Formik } from "formik";
|
import { Formik } from "formik";
|
||||||
import { LoadingButton } from "@mui/lab";
|
import { LoadingButton } from "@mui/lab";
|
||||||
import ApiModal from "../../components/apiModal";
|
import ApiModal from "../../components/apiModal";
|
||||||
|
import { isDomain } from "../../utils/indexs";
|
||||||
|
import ConfirmModal from "../../components/confirmModal";
|
||||||
|
import UploadButtons from "../../components/fileUpload";
|
||||||
|
|
||||||
|
const getDefaultConstellationHostname = (config) => {
|
||||||
|
// if domain is set, use it
|
||||||
|
if(isDomain(config.HTTPConfig.Hostname)) {
|
||||||
|
return "vpn." + config.HTTPConfig.Hostname;
|
||||||
|
} else {
|
||||||
|
return config.HTTPConfig.Hostname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const ConstellationIndex = () => {
|
export const ConstellationIndex = () => {
|
||||||
const [isAdmin, setIsAdmin] = useState(false);
|
const [isAdmin, setIsAdmin] = useState(false);
|
||||||
|
@ -41,6 +53,8 @@ export const ConstellationIndex = () => {
|
||||||
return <DesktopOutlined />
|
return <DesktopOutlined />
|
||||||
} else if (r.deviceName.toLowerCase().includes("tablet")) {
|
} else if (r.deviceName.toLowerCase().includes("tablet")) {
|
||||||
return <TabletOutlined />
|
return <TabletOutlined />
|
||||||
|
} else if (r.deviceName.toLowerCase().includes("lighthouse") || r.deviceName.toLowerCase().includes("server")) {
|
||||||
|
return <CompassOutlined />
|
||||||
} else {
|
} else {
|
||||||
return <CloudOutlined />
|
return <CloudOutlined />
|
||||||
}
|
}
|
||||||
|
@ -53,22 +67,30 @@ export const ConstellationIndex = () => {
|
||||||
<div>
|
<div>
|
||||||
<MainCard title={"Constellation Setup"} content={config.constellationIP}>
|
<MainCard title={"Constellation Setup"} content={config.constellationIP}>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
|
{config.ConstellationConfig.Enabled && config.ConstellationConfig.SlaveMode && <>
|
||||||
|
<Alert severity="info">
|
||||||
|
You are currently connected to an external constellation network. Use your main Cosmos server to manage your constellation network and devices.
|
||||||
|
</Alert>
|
||||||
|
</>}
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
Enabled: config.ConstellationConfig.Enabled,
|
Enabled: config.ConstellationConfig.Enabled,
|
||||||
IsRelay: config.ConstellationConfig.NebulaConfig.Relay.AMRelay,
|
IsRelay: config.ConstellationConfig.NebulaConfig.Relay.AMRelay,
|
||||||
|
ConstellationHostname: (config.ConstellationConfig.ConstellationHostname && config.ConstellationConfig.ConstellationHostname != "") ? config.ConstellationConfig.ConstellationHostname :
|
||||||
|
getDefaultConstellationHostname(config)
|
||||||
}}
|
}}
|
||||||
onSubmit={(values) => {
|
onSubmit={(values) => {
|
||||||
let newConfig = { ...config };
|
let newConfig = { ...config };
|
||||||
newConfig.ConstellationConfig.Enabled = values.Enabled;
|
newConfig.ConstellationConfig.Enabled = values.Enabled;
|
||||||
newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay;
|
newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay;
|
||||||
|
newConfig.ConstellationConfig.ConstellationHostname = values.ConstellationHostname;
|
||||||
return API.config.set(newConfig);
|
return API.config.set(newConfig);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(formik) => (
|
{(formik) => (
|
||||||
<form onSubmit={formik.handleSubmit}>
|
<form onSubmit={formik.handleSubmit}>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Stack spacing={2} direction="row">
|
{formik.values.Enabled && <Stack spacing={2} direction="row">
|
||||||
<Button
|
<Button
|
||||||
disableElevation
|
disableElevation
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
@ -77,14 +99,40 @@ export const ConstellationIndex = () => {
|
||||||
await API.constellation.restart();
|
await API.constellation.restart();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Restart Nebula
|
Restart VPN Service
|
||||||
</Button>
|
</Button>
|
||||||
<ApiModal callback={API.constellation.getLogs} label={"Show Nebula logs"} />
|
<ApiModal callback={API.constellation.getLogs} label={"Show VPN logs"} />
|
||||||
<ApiModal callback={API.constellation.getConfig} label={"Render Nebula Config"} />
|
<ApiModal callback={API.constellation.getConfig} label={"Show VPN Config"} />
|
||||||
</Stack>
|
<ConfirmModal
|
||||||
|
variant="outlined"
|
||||||
|
color="warning"
|
||||||
|
label={"Reset Network"}
|
||||||
|
content={"This will completely reset the network, and disconnect all the clients. You will need to reconnect them. This cannot be undone."}
|
||||||
|
callback={async () => {
|
||||||
|
await API.constellation.reset();
|
||||||
|
refreshConfig();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>}
|
||||||
<CosmosCheckbox formik={formik} name="Enabled" label="Constellation Enabled" />
|
<CosmosCheckbox formik={formik} name="Enabled" label="Constellation Enabled" />
|
||||||
<CosmosCheckbox formik={formik} name="IsRelay" label="Relay requests via this Node" />
|
{config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <>
|
||||||
|
{formik.values.Enabled && <>
|
||||||
|
<CosmosCheckbox formik={formik} name="IsRelay" label="Relay requests via this Node" />
|
||||||
|
<Alert severity="info">This is your Constellation hostname, that you will use to connect. If you are using a domain name, this needs to be different from your server's hostname. Whatever the domain you choose, it is very important that you make sure there is a A entry in your domain DNS pointing to this server. <strong>If you change this value, you will need to reset your network and reconnect all the clients!</strong></Alert>
|
||||||
|
<CosmosInputText formik={formik} name="ConstellationHostname" label="Constellation Hostname" />
|
||||||
|
</>}
|
||||||
|
</>}
|
||||||
|
<UploadButtons
|
||||||
|
accept=".yml,.yaml"
|
||||||
|
label={"Upload Nebula Config"}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
OnChange={async (e) => {
|
||||||
|
let file = e.target.files[0];
|
||||||
|
await API.constellation.connect(file);
|
||||||
|
refreshConfig();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
disableElevation
|
disableElevation
|
||||||
loading={formik.isSubmitting}
|
loading={formik.isSubmitting}
|
||||||
|
@ -101,12 +149,13 @@ export const ConstellationIndex = () => {
|
||||||
</Stack>
|
</Stack>
|
||||||
</MainCard>
|
</MainCard>
|
||||||
</div>
|
</div>
|
||||||
|
{config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <>
|
||||||
<CosmosFormDivider title={"Devices"} />
|
<CosmosFormDivider title={"Devices"} />
|
||||||
<PrettyTableView
|
<PrettyTableView
|
||||||
data={devices}
|
data={devices}
|
||||||
getKey={(r) => r.deviceName}
|
getKey={(r) => r.deviceName}
|
||||||
buttons={[
|
buttons={[
|
||||||
<AddDeviceModal isAdmin={isAdmin} users={users} config={config} refreshConfig={refreshConfig} devices={devices} />
|
<AddDeviceModal isAdmin={isAdmin} users={users} config={config} refreshConfig={refreshConfig} devices={devices} />,
|
||||||
]}
|
]}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
|
@ -121,6 +170,10 @@ export const ConstellationIndex = () => {
|
||||||
title: 'Owner',
|
title: 'Owner',
|
||||||
field: (r) => <strong>{r.nickname}</strong>,
|
field: (r) => <strong>{r.nickname}</strong>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Type',
|
||||||
|
field: (r) => <strong>{r.isLighthouse ? "Lighthouse" : "Client"}</strong>,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Constellation IP',
|
title: 'Constellation IP',
|
||||||
screenMin: 'md',
|
screenMin: 'md',
|
||||||
|
@ -137,6 +190,7 @@ export const ConstellationIndex = () => {
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
</>}
|
||||||
</Stack>
|
</Stack>
|
||||||
</> : <center>
|
</> : <center>
|
||||||
<CircularProgress color="inherit" size={20} />
|
<CircularProgress color="inherit" size={20} />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cosmos-server",
|
"name": "cosmos-server",
|
||||||
"version": "0.10.0-unstable14",
|
"version": "0.10.0-unstable15",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "test-server.js",
|
"main": "test-server.js",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
@ -9,10 +9,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeviceCreateRequestJSON struct {
|
type DeviceCreateRequestJSON struct {
|
||||||
Nickname string `json:"nickname",validate:"required,min=3,max=32,alphanum"`
|
|
||||||
DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"`
|
DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"`
|
||||||
IP string `json:"ip",validate:"required,ipv4"`
|
IP string `json:"ip",validate:"required,ipv4"`
|
||||||
PublicKey string `json:"publicKey",omitempty`
|
PublicKey string `json:"publicKey",omitempty`
|
||||||
|
|
||||||
|
// for devices only
|
||||||
|
Nickname string `json:"nickname",validate:"max=32,alphanum",omitempty`
|
||||||
|
|
||||||
|
// for lighthouse only
|
||||||
|
IsLighthouse bool `json:"isLighthouse",omitempty`
|
||||||
|
IsRelay bool `json:"isRelay",omitempty`
|
||||||
|
PublicHostname string `json:"PublicHostname",omitempty`
|
||||||
|
Port string `json:"port",omitempty`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeviceCreate(w http.ResponseWriter, req *http.Request) {
|
func DeviceCreate(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -67,11 +75,22 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if request.IsLighthouse && request.Nickname != "" {
|
||||||
|
utils.Error("DeviceCreation: Lighthouse cannot belong to a user", nil)
|
||||||
|
utils.HTTPError(w, "Device Creation Error: Lighthouse cannot have a nickname",
|
||||||
|
http.StatusInternalServerError, "DC003")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
_, err3 := c.InsertOne(nil, map[string]interface{}{
|
_, err3 := c.InsertOne(nil, map[string]interface{}{
|
||||||
"Nickname": nickname,
|
"Nickname": nickname,
|
||||||
"DeviceName": deviceName,
|
"DeviceName": deviceName,
|
||||||
"PublicKey": key,
|
"PublicKey": key,
|
||||||
"IP": request.IP,
|
"IP": request.IP,
|
||||||
|
"IsLighthouse": request.IsLighthouse,
|
||||||
|
"IsRelay": request.IsRelay,
|
||||||
|
"PublicHostname": request.PublicHostname,
|
||||||
|
"Port": request.Port,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err3 != nil {
|
if err3 != nil {
|
||||||
|
@ -88,9 +107,24 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) {
|
||||||
http.StatusInternalServerError, "DC006")
|
http.StatusInternalServerError, "DC006")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lightHousesList := []utils.ConstellationDevice{}
|
||||||
|
if request.IsLighthouse {
|
||||||
|
lightHousesList, err = GetAllLightHouses()
|
||||||
|
}
|
||||||
|
|
||||||
// read configYml from config/nebula.yml
|
// read configYml from config/nebula.yml
|
||||||
configYml, err := getYAMLClientConfig(deviceName, utils.CONFIGFOLDER + "nebula.yml", capki, cert, key)
|
configYml, err := getYAMLClientConfig(deviceName, utils.CONFIGFOLDER + "nebula.yml", capki, cert, key, utils.ConstellationDevice{
|
||||||
|
Nickname: nickname,
|
||||||
|
DeviceName: deviceName,
|
||||||
|
PublicKey: key,
|
||||||
|
IP: request.IP,
|
||||||
|
IsLighthouse: request.IsLighthouse,
|
||||||
|
IsRelay: request.IsRelay,
|
||||||
|
PublicHostname: request.PublicHostname,
|
||||||
|
Port: request.Port,
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("DeviceCreation: Error while reading config", err)
|
utils.Error("DeviceCreation: Error while reading config", err)
|
||||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||||
|
@ -108,6 +142,11 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) {
|
||||||
"IP": request.IP,
|
"IP": request.IP,
|
||||||
"Config": configYml,
|
"Config": configYml,
|
||||||
"CA": capki,
|
"CA": capki,
|
||||||
|
"IsLighthouse": request.IsLighthouse,
|
||||||
|
"IsRelay": request.IsRelay,
|
||||||
|
"PublicHostname": request.PublicHostname,
|
||||||
|
"Port": request.Port,
|
||||||
|
"LighthousesList": lightHousesList,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else if err2 == nil {
|
} else if err2 == nil {
|
||||||
|
|
|
@ -29,7 +29,7 @@ func DeviceList(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var devices []utils.Device
|
var devices []utils.ConstellationDevice
|
||||||
|
|
||||||
// Check if user is an admin
|
// Check if user is an admin
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
|
@ -47,11 +47,6 @@ func DeviceList(w http.ResponseWriter, req *http.Request) {
|
||||||
utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL002")
|
utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL002")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the private key from the response
|
|
||||||
for i := range devices {
|
|
||||||
devices[i].PrivateKey = ""
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// If not admin, get user's devices based on their nickname
|
// If not admin, get user's devices based on their nickname
|
||||||
nickname := req.Header.Get("x-cosmos-user")
|
nickname := req.Header.Get("x-cosmos-user")
|
||||||
|
@ -68,11 +63,6 @@ func DeviceList(w http.ResponseWriter, req *http.Request) {
|
||||||
utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL004")
|
utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL004")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the private key from the response
|
|
||||||
for i := range devices {
|
|
||||||
devices[i].PrivateKey = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with the list of devices
|
// Respond with the list of devices
|
||||||
|
|
|
@ -55,6 +55,26 @@ func API_Restart(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func API_Reset(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if utils.AdminOnly(w, req) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.Method == "GET") {
|
||||||
|
ResetNebula()
|
||||||
|
|
||||||
|
utils.Log("Constellation: nebula reset")
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"status": "OK",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||||
|
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func API_GetLogs(w http.ResponseWriter, req *http.Request) {
|
func API_GetLogs(w http.ResponseWriter, req *http.Request) {
|
||||||
if utils.AdminOnly(w, req) != nil {
|
if utils.AdminOnly(w, req) != nil {
|
||||||
return
|
return
|
||||||
|
|
47
src/constellation/api_nebula_connect.go
Normal file
47
src/constellation/api_nebula_connect.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package constellation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
|
||||||
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func API_ConnectToExisting(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if utils.AdminOnly(w, req) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.Method == "POST") {
|
||||||
|
body, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("API_Restart: Invalid User Request", err)
|
||||||
|
utils.HTTPError(w, "API_Restart Error",
|
||||||
|
http.StatusInternalServerError, "AR001")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := utils.ReadConfigFromFile()
|
||||||
|
config.ConstellationConfig.Enabled = true
|
||||||
|
config.ConstellationConfig.SlaveMode = true
|
||||||
|
config.ConstellationConfig.DNS = false
|
||||||
|
// ConstellationHostname =
|
||||||
|
|
||||||
|
// output utils.CONFIGFOLDER + "nebula.yml"
|
||||||
|
err = ioutil.WriteFile(utils.CONFIGFOLDER + "nebula.yml", body, 0644)
|
||||||
|
|
||||||
|
utils.SetBaseMainConfig(config)
|
||||||
|
|
||||||
|
RestartNebula()
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"status": "OK",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||||
|
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,32 +6,36 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
|
var err error
|
||||||
|
|
||||||
// if Constellation is enabled
|
// if Constellation is enabled
|
||||||
if utils.GetMainConfig().ConstellationConfig.Enabled {
|
if utils.GetMainConfig().ConstellationConfig.Enabled {
|
||||||
InitConfig()
|
if !utils.GetMainConfig().ConstellationConfig.SlaveMode {
|
||||||
|
InitConfig()
|
||||||
utils.Log("Initializing Constellation module...")
|
|
||||||
|
utils.Log("Initializing Constellation module...")
|
||||||
|
|
||||||
// check if ca.crt exists
|
// check if ca.crt exists
|
||||||
if _, err := os.Stat(utils.CONFIGFOLDER + "ca.crt"); os.IsNotExist(err) {
|
if _, err = os.Stat(utils.CONFIGFOLDER + "ca.crt"); os.IsNotExist(err) {
|
||||||
utils.Log("Constellation: ca.crt not found, generating...")
|
utils.Log("Constellation: ca.crt not found, generating...")
|
||||||
// generate ca.crt
|
// generate ca.crt
|
||||||
generateNebulaCACert("Cosmos - " + utils.GetMainConfig().HTTPConfig.Hostname)
|
generateNebulaCACert("Cosmos - " + utils.GetMainConfig().ConstellationConfig.ConstellationHostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if cosmos.crt exists
|
// check if cosmos.crt exists
|
||||||
if _, err := os.Stat(utils.CONFIGFOLDER + "cosmos.crt"); os.IsNotExist(err) {
|
if _, err := os.Stat(utils.CONFIGFOLDER + "cosmos.crt"); os.IsNotExist(err) {
|
||||||
utils.Log("Constellation: cosmos.crt not found, generating...")
|
utils.Log("Constellation: cosmos.crt not found, generating...")
|
||||||
// generate cosmos.crt
|
// generate cosmos.crt
|
||||||
generateNebulaCert("cosmos", "192.168.201.1/24", "", true)
|
generateNebulaCert("cosmos", "192.168.201.1/24", "", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// export nebula.yml
|
// export nebula.yml
|
||||||
utils.Log("Constellation: exporting nebula.yml...")
|
utils.Log("Constellation: exporting nebula.yml...")
|
||||||
err := ExportConfigToYAML(utils.GetMainConfig().ConstellationConfig, utils.CONFIGFOLDER + "nebula.yml")
|
err := ExportConfigToYAML(utils.GetMainConfig().ConstellationConfig, utils.CONFIGFOLDER + "nebula.yml")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("Constellation: error while exporting nebula.yml", err)
|
utils.Error("Constellation: error while exporting nebula.yml", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// start nebula
|
// start nebula
|
||||||
|
|
|
@ -80,18 +80,92 @@ func RestartNebula() {
|
||||||
Init()
|
Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ResetNebula() error {
|
||||||
|
stop()
|
||||||
|
utils.Log("Resetting nebula...")
|
||||||
|
os.RemoveAll(utils.CONFIGFOLDER + "nebula.yml")
|
||||||
|
os.RemoveAll(utils.CONFIGFOLDER + "ca.crt")
|
||||||
|
os.RemoveAll(utils.CONFIGFOLDER + "ca.key")
|
||||||
|
os.RemoveAll(utils.CONFIGFOLDER + "cosmos.crt")
|
||||||
|
os.RemoveAll(utils.CONFIGFOLDER + "cosmos.key")
|
||||||
|
// remove everything in db
|
||||||
|
|
||||||
|
c, err := utils.GetCollection(utils.GetRootAppId(), "devices")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.DeleteMany(nil, map[string]interface{}{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Init()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllLightHouses() ([]utils.ConstellationDevice, error) {
|
||||||
|
c, err := utils.GetCollection(utils.GetRootAppId(), "devices")
|
||||||
|
if err != nil {
|
||||||
|
return []utils.ConstellationDevice{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var devices []utils.ConstellationDevice
|
||||||
|
|
||||||
|
cursor, err := c.Find(nil, map[string]interface{}{
|
||||||
|
"IsLighthouse": true,
|
||||||
|
})
|
||||||
|
cursor.All(nil, &devices)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []utils.ConstellationDevice{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanIp(ip string) string {
|
||||||
|
return strings.Split(ip, "/")[0]
|
||||||
|
}
|
||||||
|
|
||||||
func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath string) error {
|
func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath string) error {
|
||||||
// Combine defaultConfig and overwriteConfig
|
// Combine defaultConfig and overwriteConfig
|
||||||
finalConfig := NebulaDefaultConfig
|
finalConfig := NebulaDefaultConfig
|
||||||
|
|
||||||
finalConfig.StaticHostMap = map[string][]string{
|
finalConfig.StaticHostMap = map[string][]string{
|
||||||
"192.168.201.1": []string{
|
"192.168.201.1": []string{
|
||||||
utils.GetMainConfig().HTTPConfig.Hostname + ":4242",
|
utils.GetMainConfig().ConstellationConfig.ConstellationHostname + ":4242",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for each lighthouse
|
||||||
|
lh, err := GetAllLightHouses()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range lh {
|
||||||
|
finalConfig.StaticHostMap[cleanIp(l.IP)] = []string{
|
||||||
|
l.PublicHostname + ":" + l.Port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add other lighthouses
|
||||||
|
finalConfig.Lighthouse.Hosts = []string{}
|
||||||
|
for _, l := range lh {
|
||||||
|
finalConfig.Lighthouse.Hosts = append(finalConfig.Lighthouse.Hosts, cleanIp(l.IP))
|
||||||
|
}
|
||||||
|
|
||||||
finalConfig.Relay.AMRelay = overwriteConfig.NebulaConfig.Relay.AMRelay
|
finalConfig.Relay.AMRelay = overwriteConfig.NebulaConfig.Relay.AMRelay
|
||||||
|
|
||||||
|
finalConfig.Relay.Relays = []string{}
|
||||||
|
for _, l := range lh {
|
||||||
|
if l.IsRelay && l.IsLighthouse {
|
||||||
|
finalConfig.Relay.Relays = append(finalConfig.Relay.Relays, cleanIp(l.IP))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Marshal the combined config to YAML
|
// Marshal the combined config to YAML
|
||||||
yamlData, err := yaml.Marshal(finalConfig)
|
yamlData, err := yaml.Marshal(finalConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -118,7 +192,7 @@ func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath st
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getYAMLClientConfig(name, configPath, capki, cert, key string) (string, error) {
|
func getYAMLClientConfig(name, configPath, capki, cert, key string, device utils.ConstellationDevice) (string, error) {
|
||||||
utils.Log("Exporting YAML config for " + name + " with file " + configPath)
|
utils.Log("Exporting YAML config for " + name + " with file " + configPath)
|
||||||
|
|
||||||
// Read the YAML config file
|
// Read the YAML config file
|
||||||
|
@ -134,21 +208,38 @@ func getYAMLClientConfig(name, configPath, capki, cert, key string) (string, err
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lh, err := GetAllLightHouses()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
if staticHostMap, ok := configMap["static_host_map"].(map[interface{}]interface{}); ok {
|
if staticHostMap, ok := configMap["static_host_map"].(map[interface{}]interface{}); ok {
|
||||||
staticHostMap["192.168.201.1"] = []string{
|
staticHostMap["192.168.201.1"] = []string{
|
||||||
utils.GetMainConfig().HTTPConfig.Hostname + ":4242",
|
utils.GetMainConfig().ConstellationConfig.ConstellationHostname + ":4242",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range lh {
|
||||||
|
staticHostMap[cleanIp(l.IP)] = []string{
|
||||||
|
l.PublicHostname + ":" + l.Port,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return "", errors.New("static_host_map not found in nebula.yml")
|
return "", errors.New("static_host_map not found in nebula.yml")
|
||||||
}
|
}
|
||||||
|
|
||||||
// set lightHouse to false
|
// set lightHouse
|
||||||
if lighthouseMap, ok := configMap["lighthouse"].(map[interface{}]interface{}); ok {
|
if lighthouseMap, ok := configMap["lighthouse"].(map[interface{}]interface{}); ok {
|
||||||
lighthouseMap["am_lighthouse"] = false
|
lighthouseMap["am_lighthouse"] = device.IsLighthouse
|
||||||
|
|
||||||
lighthouseMap["hosts"] = []string{
|
lighthouseMap["hosts"] = []string{
|
||||||
"192.168.201.1",
|
"192.168.201.1",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, l := range lh {
|
||||||
|
if cleanIp(l.IP) != cleanIp(device.IP) {
|
||||||
|
lighthouseMap["hosts"] = append(lighthouseMap["hosts"].([]string), cleanIp(l.IP))
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return "", errors.New("lighthouse not found in nebula.yml")
|
return "", errors.New("lighthouse not found in nebula.yml")
|
||||||
}
|
}
|
||||||
|
@ -162,13 +253,34 @@ func getYAMLClientConfig(name, configPath, capki, cert, key string) (string, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if relayMap, ok := configMap["relay"].(map[interface{}]interface{}); ok {
|
if relayMap, ok := configMap["relay"].(map[interface{}]interface{}); ok {
|
||||||
relayMap["am_relay"] = false
|
relayMap["am_relay"] = device.IsRelay && device.IsLighthouse
|
||||||
relayMap["relays"] = []string{"192.168.201.1"}
|
relayMap["relays"] = []string{}
|
||||||
|
if utils.GetMainConfig().ConstellationConfig.NebulaConfig.Relay.AMRelay {
|
||||||
|
relayMap["relays"] = append(relayMap["relays"].([]string), "192.168.201.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range lh {
|
||||||
|
if l.IsRelay && l.IsLighthouse && cleanIp(l.IP) != cleanIp(device.IP) {
|
||||||
|
relayMap["relays"] = append(relayMap["relays"].([]string), cleanIp(l.IP))
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return "", errors.New("relay not found in nebula.yml")
|
return "", errors.New("relay not found in nebula.yml")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if listen, ok := configMap["listen"].(map[interface{}]interface{}); ok {
|
||||||
|
if device.Port != "" {
|
||||||
|
listen["port"] = device.Port
|
||||||
|
} else {
|
||||||
|
listen["port"] = "4242"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "", errors.New("listen not found in nebula.yml")
|
||||||
|
}
|
||||||
|
|
||||||
configMap["deviceName"] = name
|
configMap["deviceName"] = name
|
||||||
|
configMap["local_dns_overwrite"] = "192.168.201.1"
|
||||||
|
configMap["public_hostname"] = device.PublicHostname
|
||||||
|
|
||||||
// export configMap as YML
|
// export configMap as YML
|
||||||
yamlData, err = yaml.Marshal(configMap)
|
yamlData, err = yaml.Marshal(configMap)
|
||||||
|
|
|
@ -334,6 +334,8 @@ func InitServer() *mux.Router {
|
||||||
|
|
||||||
srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
|
srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
|
||||||
srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart)
|
srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart)
|
||||||
|
srapi.HandleFunc("/api/constellation/reset", constellation.API_Reset)
|
||||||
|
srapi.HandleFunc("/api/constellation/connect", constellation.API_ConnectToExisting)
|
||||||
srapi.HandleFunc("/api/constellation/config", constellation.API_GetConfig)
|
srapi.HandleFunc("/api/constellation/config", constellation.API_GetConfig)
|
||||||
srapi.HandleFunc("/api/constellation/logs", constellation.API_GetLogs)
|
srapi.HandleFunc("/api/constellation/logs", constellation.API_GetLogs)
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
|
||||||
|
|
||||||
|
|
||||||
// NewProxy takes target host and creates a reverse proxy
|
// NewProxy takes target host and creates a reverse proxy
|
||||||
func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardHeader bool, DisableHeaderHardening bool, CORSOrigin string) (*httputil.ReverseProxy, error) {
|
func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardHeader bool, DisableHeaderHardening bool, CORSOrigin string, route utils.ProxyRouteConfig) (*httputil.ReverseProxy, error) {
|
||||||
url, err := url.Parse(targetHost)
|
url, err := url.Parse(targetHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -76,15 +76,28 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardH
|
||||||
req.Header.Set("X-Forwarded-Ssl", "on")
|
req.Header.Set("X-Forwarded-Ssl", "on")
|
||||||
}
|
}
|
||||||
|
|
||||||
if CORSOrigin != "" {
|
req.Header.Del("X-Origin-Host")
|
||||||
req.Header.Set("X-Forwarded-Host", url.Host)
|
req.Header.Del("X-Forwarded-Host")
|
||||||
|
req.Header.Del("X-Forwarded-For")
|
||||||
|
req.Header.Del("X-Real-Ip")
|
||||||
|
req.Header.Del("Host")
|
||||||
|
|
||||||
|
hostname := utils.GetMainConfig().HTTPConfig.Hostname
|
||||||
|
if route.Host != "" && route.UseHost {
|
||||||
|
hostname = route.Host
|
||||||
|
}
|
||||||
|
if route.UsePathPrefix {
|
||||||
|
hostname = hostname + route.PathPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
if VerboseForwardHeader {
|
if VerboseForwardHeader {
|
||||||
req.Header.Set("X-Origin-Host", url.Host)
|
req.Header.Set("X-Origin-Host", hostname)
|
||||||
req.Header.Set("Host", url.Host)
|
req.Header.Set("Host", hostname)
|
||||||
|
req.Header.Set("X-Forwarded-Host", hostname)
|
||||||
req.Header.Set("X-Forwarded-For", utils.GetClientIP(req))
|
req.Header.Set("X-Forwarded-For", utils.GetClientIP(req))
|
||||||
req.Header.Set("X-Real-IP", utils.GetClientIP(req))
|
req.Header.Set("X-Real-IP", utils.GetClientIP(req))
|
||||||
|
} else {
|
||||||
|
req.Host = url.Host
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,8 +113,6 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardH
|
||||||
|
|
||||||
if CORSOrigin != "" {
|
if CORSOrigin != "" {
|
||||||
resp.Header.Del("Access-Control-Allow-Origin")
|
resp.Header.Del("Access-Control-Allow-Origin")
|
||||||
resp.Header.Del("Access-Control-Allow-Methods")
|
|
||||||
resp.Header.Del("Access-Control-Allow-Headers")
|
|
||||||
resp.Header.Del("Access-Control-Allow-Credentials")
|
resp.Header.Del("Access-Control-Allow-Credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +137,7 @@ func RouteTo(route utils.ProxyRouteConfig) http.Handler {
|
||||||
routeType := route.Mode
|
routeType := route.Mode
|
||||||
|
|
||||||
if(routeType == "SERVAPP" || routeType == "PROXY") {
|
if(routeType == "SERVAPP" || routeType == "PROXY") {
|
||||||
proxy, err := NewProxy(destination, route.AcceptInsecureHTTPSTarget, route.VerboseForwardHeader, route.DisableHeaderHardening, route.CORSOrigin)
|
proxy, err := NewProxy(destination, route.AcceptInsecureHTTPSTarget, route.VerboseForwardHeader, route.DisableHeaderHardening, route.CORSOrigin, route)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("Create Route", err)
|
utils.Error("Create Route", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,12 +30,12 @@ func tokenMiddleware(enabled bool, adminOnly bool) func(next http.Handler) http.
|
||||||
r.Header.Set("x-cosmos-mfa", strconv.Itoa((int)(u.MFAState)))
|
r.Header.Set("x-cosmos-mfa", strconv.Itoa((int)(u.MFAState)))
|
||||||
|
|
||||||
ogcookies := r.Header.Get("Cookie")
|
ogcookies := r.Header.Get("Cookie")
|
||||||
cookieRemoveRegex := regexp.MustCompile(`jwttoken=[^;]*;`)
|
cookieRemoveRegex := regexp.MustCompile(`\s?jwttoken=[^;]*;?\s?`)
|
||||||
cookies := cookieRemoveRegex.ReplaceAllString(ogcookies, "")
|
cookies := cookieRemoveRegex.ReplaceAllString(ogcookies, "")
|
||||||
r.Header.Set("Cookie", cookies)
|
r.Header.Set("Cookie", cookies)
|
||||||
|
|
||||||
// Replace the token with a application speicfic one
|
// Replace the token with a application speicfic one
|
||||||
r.Header.Set("x-cosmos-token", "1234567890")
|
//r.Header.Set("x-cosmos-token", "1234567890")
|
||||||
|
|
||||||
if enabled && adminOnly {
|
if enabled && adminOnly {
|
||||||
if errT := utils.AdminOnlyWithRedirect(w, r); errT != nil {
|
if errT := utils.AdminOnlyWithRedirect(w, r); errT != nil {
|
||||||
|
|
|
@ -82,8 +82,6 @@ func CORSHeader(origin string) func(next http.Handler) http.Handler {
|
||||||
|
|
||||||
if origin != "" {
|
if origin != "" {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
|
||||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -211,6 +211,7 @@ type MarketSource struct {
|
||||||
|
|
||||||
type ConstellationConfig struct {
|
type ConstellationConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
SlaveMode bool
|
||||||
DNS bool
|
DNS bool
|
||||||
DNSPort string
|
DNSPort string
|
||||||
DNSFallback string
|
DNSFallback string
|
||||||
|
@ -218,6 +219,18 @@ type ConstellationConfig struct {
|
||||||
DNSAdditionalBlocklists []string
|
DNSAdditionalBlocklists []string
|
||||||
CustomDNSEntries map[string]string
|
CustomDNSEntries map[string]string
|
||||||
NebulaConfig NebulaConfig
|
NebulaConfig NebulaConfig
|
||||||
|
ConstellationHostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConstellationDevice struct {
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
DeviceName string `json:"deviceName"`
|
||||||
|
PublicKey string `json:"publicKey"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
IsLighthouse bool `json:"isLighthouse"`
|
||||||
|
IsRelay bool `json:"isRelay"`
|
||||||
|
PublicHostname string `json:"publicHostname"`
|
||||||
|
Port string `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NebulaFirewallRule struct {
|
type NebulaFirewallRule struct {
|
||||||
|
|
|
@ -214,6 +214,15 @@ func LoadBaseMainConfig(config Config) {
|
||||||
if MainConfig.DockerConfig.DefaultDataPath == "" {
|
if MainConfig.DockerConfig.DefaultDataPath == "" {
|
||||||
MainConfig.DockerConfig.DefaultDataPath = "/usr"
|
MainConfig.DockerConfig.DefaultDataPath = "/usr"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if MainConfig.ConstellationConfig.ConstellationHostname == "" {
|
||||||
|
// if hostname is a domain add vpn. suffix otherwise use hostname
|
||||||
|
if IsDomain(MainConfig.HTTPConfig.Hostname) {
|
||||||
|
MainConfig.ConstellationConfig.ConstellationHostname = "vpn." + MainConfig.HTTPConfig.Hostname
|
||||||
|
} else {
|
||||||
|
MainConfig.ConstellationConfig.ConstellationHostname = MainConfig.HTTPConfig.Hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMainConfig() Config {
|
func GetMainConfig() Config {
|
||||||
|
@ -577,4 +586,12 @@ func GetClientIP(req *http.Request) string {
|
||||||
ip = req.RemoteAddr
|
ip = req.RemoteAddr
|
||||||
}*/
|
}*/
|
||||||
return req.RemoteAddr
|
return req.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsDomain(domain string) bool {
|
||||||
|
// contains . and at least a letter and no special characters invalid in a domain
|
||||||
|
if strings.Contains(domain, ".") && strings.ContainsAny(domain, "abcdefghijklmnopqrstuvwxyz") && !strings.ContainsAny(domain, " !@#$%^&*()+=[]{}\\|;:'\",/<>?") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
Loading…
Reference in a new issue