[release] v0.10.0-unstable15
This commit is contained in:
parent
ef37940742
commit
a6b96bc42a
|
@ -6,7 +6,11 @@
|
|||
## Version 0.10.0
|
||||
- Added Constellation
|
||||
- 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
|
||||
- Add country whitelist option to geoblocker
|
||||
- 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() {
|
||||
return wrap(fetch('/cosmos/api/constellation/config', {
|
||||
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 {
|
||||
list,
|
||||
addDevice,
|
||||
restart,
|
||||
getConfig,
|
||||
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 { UploadOutlined } from '@ant-design/icons';
|
||||
|
||||
export default function UploadButtons({OnChange, accept, label}) {
|
||||
export default function UploadButtons({OnChange, accept, label, variant, fullWidth, size}) {
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
|
@ -14,7 +14,8 @@ export default function UploadButtons({OnChange, accept, label}) {
|
|||
onChange={OnChange}
|
||||
/>
|
||||
<label htmlFor="contained-button-file">
|
||||
<Button variant="contained" component="span" startIcon={<UploadOutlined />}>
|
||||
<Button variant={variant || "contained"} component="span"
|
||||
fullWidth={fullWidth} startIcon={<UploadOutlined />}>
|
||||
{label || 'Upload'}
|
||||
</Button>
|
||||
</label>
|
||||
|
|
|
@ -12,10 +12,67 @@ import { PlusCircleFilled } from '@ant-design/icons';
|
|||
import { Formik } from 'formik';
|
||||
import * as yup from 'yup';
|
||||
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 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 [openModal, setOpenModal] = useState(false);
|
||||
const [isDone, setIsDone] = useState(null);
|
||||
|
@ -63,12 +120,18 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
|||
deviceName: '',
|
||||
ip: firstIP,
|
||||
publicKey: '',
|
||||
Port: "4242",
|
||||
PublicHostname: '',
|
||||
IsRelay: true,
|
||||
isLighthouse: false,
|
||||
}}
|
||||
|
||||
validationSchema={yup.object({
|
||||
})}
|
||||
|
||||
onSubmit={(values, { setSubmitting, setStatus, setErrors }) => {
|
||||
if(values.isLighthouse) values.nickname = null;
|
||||
|
||||
return API.constellation.addDevice(values).then(({data}) => {
|
||||
setIsDone(data);
|
||||
refreshConfig();
|
||||
|
@ -85,52 +148,55 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
|||
{isDone ? <DialogContent>
|
||||
<DialogContentText>
|
||||
<p>
|
||||
Device added successfully!
|
||||
Device added successfully!
|
||||
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
|
||||
connect:
|
||||
</p>
|
||||
|
||||
<Stack spacing={2} direction={"column"}>
|
||||
<CosmosFormDivider title={"QR Code"} />
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<canvas style={{borderRadius: '15px'}} ref={canvasRef} />
|
||||
</div>
|
||||
{/* <CosmosFormDivider title={"Cosmos Client (File)"} />
|
||||
{/* {isDone.isLighthouse ? <>
|
||||
<CosmosFormDivider title={"Docker"} />
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
value={getDocker(isDone, false)}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
disabled
|
||||
/>
|
||||
<CosmosFormDivider title={"File (Docker-Compose)"} />
|
||||
<DownloadFile
|
||||
filename={isDone.DeviceName + `.constellation`}
|
||||
content={JSON.stringify(isDone, null, 2)}
|
||||
label={"Download " + isDone.DeviceName + `.constellation`}
|
||||
/> */}
|
||||
<CosmosFormDivider title={"File"} />
|
||||
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"} />
|
||||
<DownloadFile
|
||||
filename={`constellation.yml`}
|
||||
content={isDone.Config}
|
||||
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>
|
||||
</DialogContentText>
|
||||
</DialogContent> : <DialogContent>
|
||||
<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>
|
||||
<Stack spacing={2} style={{}}>
|
||||
<CosmosCheckbox
|
||||
name="isLighthouse"
|
||||
label="Lighthouse"
|
||||
formik={formik}
|
||||
/>
|
||||
{!formik.values.isLighthouse &&
|
||||
<CosmosSelect
|
||||
name="nickname"
|
||||
label="Owner"
|
||||
|
@ -141,7 +207,7 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
|||
return [u.nickname, u.nickname]
|
||||
})
|
||||
}
|
||||
/>
|
||||
/>}
|
||||
|
||||
<CosmosInputText
|
||||
name="deviceName"
|
||||
|
@ -155,12 +221,33 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
|||
formik={formik}
|
||||
/>
|
||||
|
||||
{/* <CosmosInputText
|
||||
name="Port"
|
||||
label="VPN Port (default: 4242)"
|
||||
formik={formik}
|
||||
/> */}
|
||||
|
||||
<CosmosInputText
|
||||
multiline
|
||||
name="publicKey"
|
||||
label="Public Key (Optional)"
|
||||
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>
|
||||
{formik.errors && formik.errors.length > 0 && <Stack spacing={2} direction={"column"}>
|
||||
<Alert severity="error">{formik.errors.map((err) => {
|
||||
|
@ -189,7 +276,9 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
|
|||
setIsDone(null);
|
||||
setOpenModal(true);
|
||||
}}
|
||||
variant="contained"
|
||||
variant={
|
||||
"contained"
|
||||
}
|
||||
startIcon={<PlusCircleFilled />}
|
||||
>
|
||||
Add Device
|
||||
|
|
|
@ -4,14 +4,26 @@ import * as API from "../../api";
|
|||
import AddDeviceModal from "./addDevice";
|
||||
import PrettyTableView from "../../components/tableView/prettyTableView";
|
||||
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 { Button, CircularProgress, Stack } from "@mui/material";
|
||||
import { CosmosCheckbox, CosmosFormDivider } from "../config/users/formShortcuts";
|
||||
import { Alert, Button, CircularProgress, Stack } from "@mui/material";
|
||||
import { CosmosCheckbox, CosmosFormDivider, CosmosInputText } from "../config/users/formShortcuts";
|
||||
import MainCard from "../../components/MainCard";
|
||||
import { Formik } from "formik";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
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 = () => {
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
@ -41,6 +53,8 @@ export const ConstellationIndex = () => {
|
|||
return <DesktopOutlined />
|
||||
} else if (r.deviceName.toLowerCase().includes("tablet")) {
|
||||
return <TabletOutlined />
|
||||
} else if (r.deviceName.toLowerCase().includes("lighthouse") || r.deviceName.toLowerCase().includes("server")) {
|
||||
return <CompassOutlined />
|
||||
} else {
|
||||
return <CloudOutlined />
|
||||
}
|
||||
|
@ -53,22 +67,30 @@ export const ConstellationIndex = () => {
|
|||
<div>
|
||||
<MainCard title={"Constellation Setup"} content={config.constellationIP}>
|
||||
<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
|
||||
initialValues={{
|
||||
Enabled: config.ConstellationConfig.Enabled,
|
||||
IsRelay: config.ConstellationConfig.NebulaConfig.Relay.AMRelay,
|
||||
ConstellationHostname: (config.ConstellationConfig.ConstellationHostname && config.ConstellationConfig.ConstellationHostname != "") ? config.ConstellationConfig.ConstellationHostname :
|
||||
getDefaultConstellationHostname(config)
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
let newConfig = { ...config };
|
||||
newConfig.ConstellationConfig.Enabled = values.Enabled;
|
||||
newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay;
|
||||
newConfig.ConstellationConfig.ConstellationHostname = values.ConstellationHostname;
|
||||
return API.config.set(newConfig);
|
||||
}}
|
||||
>
|
||||
{(formik) => (
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<Stack spacing={2}>
|
||||
<Stack spacing={2} direction="row">
|
||||
{formik.values.Enabled && <Stack spacing={2} direction="row">
|
||||
<Button
|
||||
disableElevation
|
||||
variant="outlined"
|
||||
|
@ -77,14 +99,40 @@ export const ConstellationIndex = () => {
|
|||
await API.constellation.restart();
|
||||
}}
|
||||
>
|
||||
Restart Nebula
|
||||
Restart VPN Service
|
||||
</Button>
|
||||
<ApiModal callback={API.constellation.getLogs} label={"Show Nebula logs"} />
|
||||
<ApiModal callback={API.constellation.getConfig} label={"Render Nebula Config"} />
|
||||
</Stack>
|
||||
<ApiModal callback={API.constellation.getLogs} label={"Show VPN logs"} />
|
||||
<ApiModal callback={API.constellation.getConfig} label={"Show VPN Config"} />
|
||||
<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="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
|
||||
disableElevation
|
||||
loading={formik.isSubmitting}
|
||||
|
@ -101,12 +149,13 @@ export const ConstellationIndex = () => {
|
|||
</Stack>
|
||||
</MainCard>
|
||||
</div>
|
||||
{config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <>
|
||||
<CosmosFormDivider title={"Devices"} />
|
||||
<PrettyTableView
|
||||
data={devices}
|
||||
getKey={(r) => r.deviceName}
|
||||
buttons={[
|
||||
<AddDeviceModal isAdmin={isAdmin} users={users} config={config} refreshConfig={refreshConfig} devices={devices} />
|
||||
<AddDeviceModal isAdmin={isAdmin} users={users} config={config} refreshConfig={refreshConfig} devices={devices} />,
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
|
@ -121,6 +170,10 @@ export const ConstellationIndex = () => {
|
|||
title: 'Owner',
|
||||
field: (r) => <strong>{r.nickname}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
field: (r) => <strong>{r.isLighthouse ? "Lighthouse" : "Client"}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Constellation IP',
|
||||
screenMin: 'md',
|
||||
|
@ -137,6 +190,7 @@ export const ConstellationIndex = () => {
|
|||
}
|
||||
]}
|
||||
/>
|
||||
</>}
|
||||
</Stack>
|
||||
</> : <center>
|
||||
<CircularProgress color="inherit" size={20} />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.10.0-unstable14",
|
||||
"version": "0.10.0-unstable15",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
|
|
@ -9,10 +9,18 @@ import (
|
|||
)
|
||||
|
||||
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"`
|
||||
IP string `json:"ip",validate:"required,ipv4"`
|
||||
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) {
|
||||
|
@ -67,11 +75,22 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) {
|
|||
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{}{
|
||||
"Nickname": nickname,
|
||||
"DeviceName": deviceName,
|
||||
"PublicKey": key,
|
||||
"IP": request.IP,
|
||||
"IsLighthouse": request.IsLighthouse,
|
||||
"IsRelay": request.IsRelay,
|
||||
"PublicHostname": request.PublicHostname,
|
||||
"Port": request.Port,
|
||||
})
|
||||
|
||||
if err3 != nil {
|
||||
|
@ -89,8 +108,23 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
lightHousesList := []utils.ConstellationDevice{}
|
||||
if request.IsLighthouse {
|
||||
lightHousesList, err = GetAllLightHouses()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
utils.Error("DeviceCreation: Error while reading config", err)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
|
@ -108,6 +142,11 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) {
|
|||
"IP": request.IP,
|
||||
"Config": configYml,
|
||||
"CA": capki,
|
||||
"IsLighthouse": request.IsLighthouse,
|
||||
"IsRelay": request.IsRelay,
|
||||
"PublicHostname": request.PublicHostname,
|
||||
"Port": request.Port,
|
||||
"LighthousesList": lightHousesList,
|
||||
},
|
||||
})
|
||||
} else if err2 == nil {
|
||||
|
|
|
@ -29,7 +29,7 @@ func DeviceList(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
var devices []utils.Device
|
||||
var devices []utils.ConstellationDevice
|
||||
|
||||
// Check if user is an admin
|
||||
if isAdmin {
|
||||
|
@ -47,11 +47,6 @@ func DeviceList(w http.ResponseWriter, req *http.Request) {
|
|||
utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL002")
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the private key from the response
|
||||
for i := range devices {
|
||||
devices[i].PrivateKey = ""
|
||||
}
|
||||
} else {
|
||||
// If not admin, get user's devices based on their nickname
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the private key from the response
|
||||
for i := range devices {
|
||||
devices[i].PrivateKey = ""
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
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() {
|
||||
var err error
|
||||
|
||||
// if Constellation is 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
|
||||
if _, err := os.Stat(utils.CONFIGFOLDER + "ca.crt"); os.IsNotExist(err) {
|
||||
utils.Log("Constellation: ca.crt not found, generating...")
|
||||
// generate ca.crt
|
||||
generateNebulaCACert("Cosmos - " + utils.GetMainConfig().HTTPConfig.Hostname)
|
||||
}
|
||||
// check if ca.crt exists
|
||||
if _, err = os.Stat(utils.CONFIGFOLDER + "ca.crt"); os.IsNotExist(err) {
|
||||
utils.Log("Constellation: ca.crt not found, generating...")
|
||||
// generate ca.crt
|
||||
generateNebulaCACert("Cosmos - " + utils.GetMainConfig().ConstellationConfig.ConstellationHostname)
|
||||
}
|
||||
|
||||
// check if cosmos.crt exists
|
||||
if _, err := os.Stat(utils.CONFIGFOLDER + "cosmos.crt"); os.IsNotExist(err) {
|
||||
utils.Log("Constellation: cosmos.crt not found, generating...")
|
||||
// generate cosmos.crt
|
||||
generateNebulaCert("cosmos", "192.168.201.1/24", "", true)
|
||||
}
|
||||
// check if cosmos.crt exists
|
||||
if _, err := os.Stat(utils.CONFIGFOLDER + "cosmos.crt"); os.IsNotExist(err) {
|
||||
utils.Log("Constellation: cosmos.crt not found, generating...")
|
||||
// generate cosmos.crt
|
||||
generateNebulaCert("cosmos", "192.168.201.1/24", "", true)
|
||||
}
|
||||
|
||||
// export nebula.yml
|
||||
utils.Log("Constellation: exporting nebula.yml...")
|
||||
err := ExportConfigToYAML(utils.GetMainConfig().ConstellationConfig, utils.CONFIGFOLDER + "nebula.yml")
|
||||
// export nebula.yml
|
||||
utils.Log("Constellation: exporting nebula.yml...")
|
||||
err := ExportConfigToYAML(utils.GetMainConfig().ConstellationConfig, utils.CONFIGFOLDER + "nebula.yml")
|
||||
|
||||
if err != nil {
|
||||
utils.Error("Constellation: error while exporting nebula.yml", err)
|
||||
if err != nil {
|
||||
utils.Error("Constellation: error while exporting nebula.yml", err)
|
||||
}
|
||||
}
|
||||
|
||||
// start nebula
|
||||
|
|
|
@ -80,18 +80,92 @@ func RestartNebula() {
|
|||
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 {
|
||||
// Combine defaultConfig and overwriteConfig
|
||||
finalConfig := NebulaDefaultConfig
|
||||
|
||||
finalConfig.StaticHostMap = map[string][]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.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
|
||||
yamlData, err := yaml.Marshal(finalConfig)
|
||||
if err != nil {
|
||||
|
@ -118,7 +192,7 @@ func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath st
|
|||
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)
|
||||
|
||||
// Read the YAML config file
|
||||
|
@ -134,21 +208,38 @@ func getYAMLClientConfig(name, configPath, capki, cert, key string) (string, err
|
|||
return "", err
|
||||
}
|
||||
|
||||
lh, err := GetAllLightHouses()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if staticHostMap, ok := configMap["static_host_map"].(map[interface{}]interface{}); ok {
|
||||
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 {
|
||||
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 {
|
||||
lighthouseMap["am_lighthouse"] = false
|
||||
lighthouseMap["am_lighthouse"] = device.IsLighthouse
|
||||
|
||||
lighthouseMap["hosts"] = []string{
|
||||
"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 {
|
||||
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 {
|
||||
relayMap["am_relay"] = false
|
||||
relayMap["relays"] = []string{"192.168.201.1"}
|
||||
relayMap["am_relay"] = device.IsRelay && device.IsLighthouse
|
||||
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 {
|
||||
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["local_dns_overwrite"] = "192.168.201.1"
|
||||
configMap["public_hostname"] = device.PublicHostname
|
||||
|
||||
// export configMap as YML
|
||||
yamlData, err = yaml.Marshal(configMap)
|
||||
|
|
|
@ -334,6 +334,8 @@ func InitServer() *mux.Router {
|
|||
|
||||
srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
|
||||
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/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
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -76,15 +76,28 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardH
|
|||
req.Header.Set("X-Forwarded-Ssl", "on")
|
||||
}
|
||||
|
||||
if CORSOrigin != "" {
|
||||
req.Header.Set("X-Forwarded-Host", url.Host)
|
||||
req.Header.Del("X-Origin-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 {
|
||||
req.Header.Set("X-Origin-Host", url.Host)
|
||||
req.Header.Set("Host", url.Host)
|
||||
req.Header.Set("X-Origin-Host", hostname)
|
||||
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-Real-IP", utils.GetClientIP(req))
|
||||
} else {
|
||||
req.Host = url.Host
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,8 +113,6 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardH
|
|||
|
||||
if CORSOrigin != "" {
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -126,7 +137,7 @@ func RouteTo(route utils.ProxyRouteConfig) http.Handler {
|
|||
routeType := route.Mode
|
||||
|
||||
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 {
|
||||
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)))
|
||||
|
||||
ogcookies := r.Header.Get("Cookie")
|
||||
cookieRemoveRegex := regexp.MustCompile(`jwttoken=[^;]*;`)
|
||||
cookieRemoveRegex := regexp.MustCompile(`\s?jwttoken=[^;]*;?\s?`)
|
||||
cookies := cookieRemoveRegex.ReplaceAllString(ogcookies, "")
|
||||
r.Header.Set("Cookie", cookies)
|
||||
|
||||
// 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 errT := utils.AdminOnlyWithRedirect(w, r); errT != nil {
|
||||
|
|
|
@ -82,8 +82,6 @@ func CORSHeader(origin string) func(next http.Handler) http.Handler {
|
|||
|
||||
if 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")
|
||||
}
|
||||
|
||||
|
|
|
@ -211,6 +211,7 @@ type MarketSource struct {
|
|||
|
||||
type ConstellationConfig struct {
|
||||
Enabled bool
|
||||
SlaveMode bool
|
||||
DNS bool
|
||||
DNSPort string
|
||||
DNSFallback string
|
||||
|
@ -218,6 +219,18 @@ type ConstellationConfig struct {
|
|||
DNSAdditionalBlocklists []string
|
||||
CustomDNSEntries map[string]string
|
||||
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 {
|
||||
|
|
|
@ -214,6 +214,15 @@ func LoadBaseMainConfig(config Config) {
|
|||
if MainConfig.DockerConfig.DefaultDataPath == "" {
|
||||
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 {
|
||||
|
@ -578,3 +587,11 @@ func GetClientIP(req *http.Request) string {
|
|||
}*/
|
||||
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