[release] v0.10.0-unstable

This commit is contained in:
Yann Stepienik 2023-08-20 11:36:52 +01:00
parent 525146a210
commit bc7aaa21d0
30 changed files with 1099 additions and 30 deletions

View File

@ -53,6 +53,24 @@ jobs:
command: |
curl -s -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=$MAX_TOKEN&suffix=tar.gz" -o GeoLite2-Country.tar.gz
tar -xzf GeoLite2-Country.tar.gz --strip-components 1 --wildcards "*.mmdb"
- run:
name: Download and Extract ARM Nebula Binary
command: |
curl -LO https://github.com/slackhq/nebula/releases/download/v1.7.2/nebula-linux-arm64.tar.gz
tar -xzvf nebula-linux-arm64.tar.gz
- run:
name: Rename ARM Nebula Binary
command: |
mv nebula nebula-arm
mv nebula-cert nebula-cert-arm
- run:
name: Download and Extract Nebula Binary
command: |
curl -LO https://github.com/slackhq/nebula/releases/download/v1.7.2/nebula-linux-amd64.tar.gz
tar -xzvf nebula-linux-amd64.tar.gz
- run:
name: Build UI

7
.gitignore vendored
View File

@ -12,4 +12,9 @@ todo.txt
LICENCE
tokens.json
.vscode
GeoLite2-Country.mmdb
GeoLite2-Country.mmdb
zz_test_config
nebula-arm
nebula-arm-cert
nebula
nebula-cert

View File

@ -18,6 +18,7 @@ echo " ---- Build complete, copy assets ----"
cp -r static build/
cp -r GeoLite2-Country.mmdb build/
cp nebula-arm-cert nebula-cert nebula-arm nebula build/
cp -r Logo.png build/
mkdir build/images
cp client/src/assets/images/icons/cosmos_gray.png build/cosmos_gray.png

View File

@ -1,6 +1,12 @@
<<<<<<< HEAD
## Version 0.9.20 - 0.9.21
- Add option to disable CORS hardening (with empty value)
=======
## Version 0.10.0
- Added Constellation
- DNS Challenge is now used for all certificates when enabled
>>>>>>> b8a9e71 ([release] v0.10.0-unstable)
## Version 0.9.19
- Add country whitelist option to geoblocker
- No countries blocked by default anymore

View File

@ -0,0 +1,25 @@
import wrap from './wrap';
function list() {
return wrap(fetch('/cosmos/api/constellation/devices', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
}))
}
function addDevice(device) {
return wrap(fetch('/cosmos/api/constellation/devices', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(device),
}))
}
export {
list,
addDevice,
};

View File

@ -0,0 +1,28 @@
import { Button } from "@mui/material";
export const DownloadFile = ({ filename, content, label }) => {
const downloadFile = () => {
// Create a blob with the content
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
// Create a link element
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = filename;
// Append the link to the document (needed for Firefox)
document.body.appendChild(link);
// Simulate a click to start the download
link.click();
// Cleanup the DOM by removing the link element
document.body.removeChild(link);
}
return (
<Button onClick={downloadFile}>
{label}
</Button>
);
}

View File

@ -3,6 +3,7 @@ import * as _users from './users';
import * as _config from './config';
import * as _docker from './docker';
import * as _market from './market';
import * as _constellation from './constellation';
import * as authDemo from './authentication.demo';
import * as usersDemo from './users.demo';
@ -211,6 +212,7 @@ let users = _users;
let config = _config;
let docker = _docker;
let market = _market;
let constellation = _constellation;
if(isDemo) {
auth = authDemo;
@ -232,6 +234,7 @@ export {
config,
docker,
market,
constellation,
getStatus,
newInstall,
isOnline,

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -1,5 +1,6 @@
// assets
import { ProfileOutlined, PicLeftOutlined, SettingOutlined, NodeExpandOutlined, AppstoreOutlined} from '@ant-design/icons';
import ConstellationIcon from '../assets/images/icons/constellation.png'
// icons
const icons = {
@ -7,7 +8,6 @@ const icons = {
ProfileOutlined,
SettingOutlined
};
// ==============================|| MENU ITEMS - EXTRA PAGES ||============================== //
const pages = {
@ -29,6 +29,14 @@ const pages = {
url: '/cosmos-ui/config-url',
icon: icons.NodeExpandOutlined,
},
{
id: 'constellation',
title: 'Constellation',
type: 'item',
url: '/cosmos-ui/constellation',
icon: () => <img height="28px" width="28px" style={{marginLeft: "-6px"}} src={ConstellationIcon} />,
},
{
id: 'users',
title: 'Users',

View File

@ -0,0 +1,147 @@
// material-ui
import { Alert, Button, Stack, TextField } 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 { useState } from 'react';
import ResponsiveButton from '../../components/responseiveButton';
import { PlusCircleFilled } from '@ant-design/icons';
import { Formik } from 'formik';
import * as yup from 'yup';
import * as API from '../../api';
import { CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
import { DownloadFile } from '../../api/downloadButton';
const AddDeviceModal = ({ config, isAdmin, refreshConfig, devices }) => {
const [openModal, setOpenModal] = useState(false);
const [isDone, setIsDone] = useState(null);
return <>
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
<Formik
initialValues={{
nickname: '',
deviceName: '',
ip: '192.168.201.1/24',
publicKey: '',
}}
validationSchema={yup.object({
})}
onSubmit={(values, { setSubmitting, setStatus, setErrors }) => {
return API.constellation.addDevice(values).then(({data}) => {
setIsDone(data);
refreshConfig();
}).catch((err) => {
setErrors(err.response.data);
});
}}
>
{(formik) => (
<form onSubmit={formik.handleSubmit}>
<DialogTitle>Manually Add Device</DialogTitle>
{isDone ? <DialogContent>
<DialogContentText>
<p>
Device added successfully!
Download the private and public keys to your device along side the config and network certificate to connect:
</p>
<Stack spacing={2} direction={"column"}>
<DownloadFile
filename={`config.yml`}
content={isDone.Config}
label={"Download config.yml"}
/>
<DownloadFile
filename={isDone.DeviceName + `.key`}
content={isDone.PrivateKey}
label={"Download " + isDone.DeviceName + `.key`}
/>
<DownloadFile
filename={isDone.DeviceName + `.crt`}
content={isDone.PublicKey}
label={"Download " + isDone.DeviceName + `.crt`}
/>
<DownloadFile
filename={`ca.crt`}
content={isDone.CA}
label={"Download ca.crt"}
/>
</Stack>
</DialogContentText>
</DialogContent> : <DialogContent>
<DialogContentText>
<p>Manually add a device to the constellation. It is recommended that you use the Cosmos app instead. Use this form to add another Nebula device manually</p>
<div>
<Stack spacing={2} style={{}}>
<CosmosSelect
name="nickname"
label="Owner"
formik={formik}
// disabled={!isAdmin}
options={[
["admin", "admin"]
]}
/>
<CosmosInputText
name="deviceName"
label="Device Name"
formik={formik}
/>
<CosmosInputText
name="ip"
label="Constellation IP Address"
formik={formik}
/>
<CosmosInputText
multiline
name="publicKey"
label="Public Key (Optional)"
formik={formik}
/>
<div>
{formik.errors && formik.errors.length > 0 && <Stack spacing={2} direction={"column"}>
<Alert severity="error">{formik.errors.map((err) => {
return <div>{err}</div>
})}</Alert>
</Stack>}
</div>
</Stack>
</div>
</DialogContentText>
</DialogContent>}
<DialogActions>
<Button onClick={() => setOpenModal(false)}>Close</Button>
<Button color="primary" variant="contained" type="submit">Add</Button>
</DialogActions>
</form>
)}
</Formik>
</Dialog>
<ResponsiveButton
color="primary"
onClick={() => {
setIsDone(null);
setOpenModal(true);
}}
variant="contained"
startIcon={<PlusCircleFilled />}
>
Manually Add Device
</ResponsiveButton>
</>;
};
export default AddDeviceModal;

View File

@ -0,0 +1,81 @@
import React from "react";
import { useEffect, useState } from "react";
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 IsLoggedIn from "../../isLoggedIn";
export const ConstellationIndex = () => {
const [isAdmin, setIsAdmin] = useState(false);
const [config, setConfig] = useState(null);
const [devices, setDevices] = useState(null);
const refreshConfig = async () => {
let configAsync = await API.config.get();
setConfig(configAsync.data);
setIsAdmin(configAsync.isAdmin);
setDevices((await API.constellation.list()).data || []);
};
useEffect(() => {
refreshConfig();
}, []);
const getIcon = (r) => {
if (r.deviceName.toLowerCase().includes("mobile") || r.deviceName.toLowerCase().includes("phone")) {
return <MobileOutlined />
}
else if (r.deviceName.toLowerCase().includes("laptop") || r.deviceName.toLowerCase().includes("computer")) {
return <LaptopOutlined />
} else if (r.deviceName.toLowerCase().includes("desktop")) {
return <DesktopOutlined />
} else if (r.deviceName.toLowerCase().includes("tablet")) {
return <TabletOutlined />
} else {
return <CloudOutlined />
}
}
return <>
<IsLoggedIn />
{devices && config && <>
<PrettyTableView
data={devices}
getKey={(r) => r.deviceName}
buttons={[
<AddDeviceModal isAdmin={isAdmin} config={config} refreshConfig={refreshConfig} devices={devices} />
]}
columns={[
{
title: '',
field: getIcon,
},
{
title: 'Device Name',
field: (r) => <strong>{r.deviceName}</strong>,
},
{
title: 'Owner',
field: (r) => <strong>{r.nickname}</strong>,
},
{
title: 'Constellation IP',
screenMin: 'md',
field: (r) => r.ip,
},
{
title: '',
clickable: true,
field: (r) => {
return <DeleteButton onDelete={async () => {
alert("caca")
}}></DeleteButton>
}
}
]}
/>
</>}
</>
};

View File

@ -15,6 +15,7 @@ import ContainerIndex from '../pages/servapps/containers';
import NewDockerServiceForm from '../pages/servapps/containers/newServiceForm';
import OpenIdList from '../pages/openid/openid-list';
import MarketPage from '../pages/market/listing';
import { ConstellationIndex } from '../pages/constellation';
// render - dashboard
@ -44,6 +45,10 @@ const MainRoutes = {
path: '/cosmos-ui/dashboard',
element: <DashboardDefault />
},
{
path: '/cosmos-ui/constellation',
element: <ConstellationIndex />
},
{
path: '/cosmos-ui/servapps',
element: <ServAppsIndex />

View File

@ -1,3 +1,5 @@
import { Button } from "@mui/material";
export const randomString = (length) => {
let text = "";
const possible =
@ -45,4 +47,4 @@ export const redirectToLocal = (url) => {
throw new Error("URL must be local");
}
window.location.href = url;
}
}

View File

@ -29,7 +29,7 @@ WORKDIR /app
COPY build/cosmos build/cosmos-arm64 ./
# Copy other resources
COPY build/cosmos_gray.png build/Logo.png build/GeoLite2-Country.mmdb build/meta.json ./
COPY build/* ./
COPY static ./static
# Run the respective binary based on the BINARY_NAME

View File

@ -13,7 +13,8 @@ RUN apt-get update \
WORKDIR /app
COPY build/cosmos build/cosmos_gray.png build/Logo.png build/GeoLite2-Country.mmdb build/meta.json ./
COPY build/* ./
COPY static ./static
CMD ["./cosmos"]

View File

@ -1,6 +1,6 @@
{
"name": "cosmos-server",
"version": "0.9.21",
"version": "0.10.0-unstable",
"description": "",
"main": "test-server.js",
"bugs": {
@ -63,13 +63,12 @@
"scripts": {
"client": "vite",
"client-build": "vite build --base=/cosmos-ui/",
"start": "env CONFIG_FILE=./config_dev.json EZ=UTC ACME_STAGING=true build/cosmos",
"start": "env COSMOS_CONFIG_FOLDER=/mnt/e/work/Cosmos-Server/zz_test_config/ CONFIG_FILE=./config_dev.json EZ=UTC ACME_STAGING=true build/cosmos",
"build": "sh build.sh",
"dev": "npm run build && npm run start",
"dockerdevbuild": "sh build.sh && docker build -f dockerfile.local --tag cosmos-dev .",
"dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run -d -p 7200:443 -p 80:80 -p 443:443 -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG -v /:/mnt/host --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
"dockerdev": "npm run dockerdevbuild && npm run dockerdevrun",
"dockerdevclient": "npm run client-build && npm run dockerdevbuild && npm run dockerdevrun",
"dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run --cap-add NET_ADMIN -d -p 7200:443 -p 80:80 -p 443:443 -p 4242:4242 -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG -v /:/mnt/host --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
"dockerdev": "npm run client-build && npm run dockerdevbuild && npm run dockerdevrun",
"demo": "vite build --base=/cosmos-ui/ --mode demo",
"devdemo": "vite --mode demo"
},

View File

@ -54,7 +54,7 @@ func UploadBackground(w http.ResponseWriter, req *http.Request) {
}
// create a new file in the config directory
dst, err := os.Create("/config/background" + ext)
dst, err := os.Create(utils.CONFIGFOLDER + "background" + ext)
if err != nil {
utils.HTTPError(w, "Error creating destination file", http.StatusInternalServerError, "FILE004")
return
@ -99,7 +99,7 @@ func GetBackground(w http.ResponseWriter, req *http.Request) {
if(req.Method == "GET") {
// get the background image
bg, err := ioutil.ReadFile("/config/background." + ext)
bg, err := ioutil.ReadFile(utils.CONFIGFOLDER + "background." + ext)
if err != nil {
utils.HTTPError(w, "Error reading background image", http.StatusInternalServerError, "FILE003")
return

View File

@ -42,6 +42,7 @@ func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
"data": config,
"updates": utils.UpdateAvailable,
"hostname": os.Getenv("HOSTNAME"),
"isAdmin": isAdmin,
})
} else {
utils.Error("SettingGet: Method not allowed" + req.Method, nil)

View File

@ -0,0 +1,129 @@
package constellation
import (
"net/http"
"encoding/json"
"go.mongodb.org/mongo-driver/mongo"
"github.com/azukaar/cosmos-server/src/utils"
)
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`
}
func DeviceCreate(w http.ResponseWriter, req *http.Request) {
if(req.Method == "POST") {
var request DeviceCreateRequestJSON
err1 := json.NewDecoder(req.Body).Decode(&request)
if err1 != nil {
utils.Error("ConstellationDeviceCreation: Invalid User Request", err1)
utils.HTTPError(w, "Device Creation Error",
http.StatusInternalServerError, "DC001")
return
}
errV := utils.Validate.Struct(request)
if errV != nil {
utils.Error("DeviceCreation: Invalid User Request", errV)
utils.HTTPError(w, "Device Creation Error: " + errV.Error(),
http.StatusInternalServerError, "DC002")
return
}
nickname := utils.Sanitize(request.Nickname)
deviceName := utils.Sanitize(request.DeviceName)
if utils.AdminOrItselfOnly(w, req, nickname) != nil {
return
}
c, errCo := utils.GetCollection(utils.GetRootAppId(), "devices")
if errCo != nil {
utils.Error("Database Connect", errCo)
utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001")
return
}
device := utils.Device{}
utils.Debug("ConstellationDeviceCreation: Creating Device " + deviceName)
err2 := c.FindOne(nil, map[string]interface{}{
"DeviceName": deviceName,
}).Decode(&device)
if err2 == mongo.ErrNoDocuments {
cert, key, err := generateNebulaCert(deviceName, request.IP, false)
if err != nil {
utils.Error("DeviceCreation: Error while creating Device", err)
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
http.StatusInternalServerError, "DC001")
return
}
_, err3 := c.InsertOne(nil, map[string]interface{}{
"Nickname": nickname,
"DeviceName": deviceName,
"PublicKey": cert,
"PrivateKey": key,
"IP": request.IP,
})
if err3 != nil {
utils.Error("DeviceCreation: Error while creating Device", err3)
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
http.StatusInternalServerError, "DC004")
return
}
// read configYml from config/nebula.yml
configYml, err := getYAMLClientConfig(deviceName, utils.CONFIGFOLDER + "nebula.yml")
if err != nil {
utils.Error("DeviceCreation: Error while reading config", err)
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
http.StatusInternalServerError, "DC005")
return
}
capki, err := getCApki()
if err != nil {
utils.Error("DeviceCreation: Error while reading ca.crt", err)
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
http.StatusInternalServerError, "DC006")
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "OK",
"data": map[string]interface{}{
"Nickname": nickname,
"DeviceName": deviceName,
"PublicKey": cert,
"PrivateKey": key,
"IP": request.IP,
"Config": configYml,
"CA": capki,
},
})
} else if err2 == nil {
utils.Error("DeviceCreation: Device already exists", nil)
utils.HTTPError(w, "Device name already exists", http.StatusConflict, "DC002")
return
} else {
utils.Error("DeviceCreation: Error while finding device", err2)
utils.HTTPError(w, "Device Creation Error: " + err2.Error(),
http.StatusInternalServerError, "DC001")
return
}
} else {
utils.Error("DeviceCreation: Method not allowed" + req.Method, nil)
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
return
}
}

View File

@ -0,0 +1,18 @@
package constellation
import (
"net/http"
"github.com/azukaar/cosmos-server/src/utils"
)
func ConstellationAPIDevices(w http.ResponseWriter, req *http.Request) {
if (req.Method == "GET") {
DeviceList(w, req)
} else if (req.Method == "POST") {
DeviceCreate(w, req)
} else {
utils.Error("UserRoute: Method not allowed" + req.Method, nil)
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
return
}
}

View File

@ -0,0 +1,83 @@
package constellation
import (
"net/http"
"encoding/json"
"github.com/azukaar/cosmos-server/src/utils"
)
func DeviceList(w http.ResponseWriter, req *http.Request) {
// Check for GET method
if req.Method != "GET" {
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP002")
return
}
if utils.LoggedInOnly(w, req) != nil {
return
}
isAdmin := utils.IsAdmin(req)
// Connect to the collection
c, errCo := utils.GetCollection(utils.GetRootAppId(), "devices")
if errCo != nil {
utils.Error("Database Connect", errCo)
utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001")
return
}
var devices []utils.Device
// Check if user is an admin
if isAdmin {
// If admin, get all devices
cursor, err := c.Find(nil, map[string]interface{}{})
if err != nil {
utils.Error("DeviceList: Error fetching devices", err)
utils.HTTPError(w, "Error fetching devices", http.StatusInternalServerError, "DL001")
return
}
defer cursor.Close(nil)
if err = cursor.All(nil, &devices); err != nil {
utils.Error("DeviceList: Error decoding devices", err)
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")
cursor, err := c.Find(nil, map[string]interface{}{"Nickname": nickname})
if err != nil {
utils.Error("DeviceList: Error fetching devices", err)
utils.HTTPError(w, "Error fetching devices", http.StatusInternalServerError, "DL003")
return
}
defer cursor.Close(nil)
if err = cursor.All(nil, &devices); err != nil {
utils.Error("DeviceList: Error decoding devices", err)
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
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "OK",
"data": devices,
})
}

View File

@ -0,0 +1,42 @@
package constellation
import (
"github.com/azukaar/cosmos-server/src/utils"
"os"
)
func Init() {
// if Constellation is enabled
if utils.GetMainConfig().ConstellationConfig.Enabled {
InitConfig()
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 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.0/24", true)
}
// export nebula.yml
utils.Log("Constellation: exporting nebula.yml...")
ExportConfigToYAML(utils.GetMainConfig().ConstellationConfig, utils.CONFIGFOLDER + "nebula.yml")
// start nebula
utils.Log("Constellation: starting nebula...")
err := startNebulaInBackground()
if err != nil {
utils.Error("Constellation: error while starting nebula", err)
}
utils.Log("Constellation module initialized")
}
}

283
src/constellation/nebula.go Normal file
View File

@ -0,0 +1,283 @@
package constellation
import (
"github.com/azukaar/cosmos-server/src/utils"
"os/exec"
"os"
"fmt"
"errors"
"runtime"
"sync"
"gopkg.in/yaml.v2"
"strings"
"io/ioutil"
"strconv"
)
var (
process *exec.Cmd
processMux sync.Mutex
)
func binaryToRun() string {
if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
return "./nebula-arm"
}
return "./nebula"
}
func startNebulaInBackground() error {
processMux.Lock()
defer processMux.Unlock()
if process != nil {
return errors.New("nebula is already running")
}
process = exec.Command(binaryToRun(), "-config", utils.CONFIGFOLDER + "nebula.yml")
process.Stderr = os.Stderr
if utils.LoggingLevelLabels[utils.GetMainConfig().LoggingLevel] == utils.DEBUG {
process.Stdout = os.Stdout
} else {
process.Stdout = nil
}
// Start the process in the background
if err := process.Start(); err != nil {
return err
}
utils.Log(fmt.Sprintf("%s started with PID %d\n", binaryToRun(), process.Process.Pid))
return nil
}
func stop() error {
processMux.Lock()
defer processMux.Unlock()
if process == nil {
return errors.New("nebula is not running")
}
if err := process.Process.Kill(); err != nil {
return err
}
process = nil
utils.Log("Stopped nebula.")
return nil
}
func restart() error {
if err := stop(); err != nil {
return err
}
return startNebulaInBackground()
}
func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath string) error {
// Combine defaultConfig and overwriteConfig
finalConfig := NebulaDefaultConfig
finalConfig.StaticHostMap = map[string][]string{
"192.168.201.0": []string{utils.GetMainConfig().HTTPConfig.Hostname + ":4242"},
}
// Marshal the combined config to YAML
yamlData, err := yaml.Marshal(finalConfig)
if err != nil {
return err
}
// Write YAML data to the specified file
yamlFile, err := os.Create(outputPath)
if err != nil {
return err
}
defer yamlFile.Close()
_, err = yamlFile.Write(yamlData)
if err != nil {
return err
}
return nil
}
func getYAMLClientConfig(name, configPath string) (string, error) {
utils.Log("Exporting YAML config for " + name + " with file " + configPath)
// Read the YAML config file
yamlData, err := ioutil.ReadFile(configPath)
if err != nil {
return "", err
}
// Unmarshal the YAML data into a map interface
var configMap map[string]interface{}
err = yaml.Unmarshal(yamlData, &configMap)
if err != nil {
return "", err
}
// set lightHouse to false
if lighthouseMap, ok := configMap["lighthouse"].(map[interface{}]interface{}); ok {
lighthouseMap["am_lighthouse"] = false
lighthouseMap["hosts"] = []string{
"192.168.201.0",
}
} else {
return "", errors.New("lighthouse not found in nebula.yml")
}
if pkiMap, ok := configMap["pki"].(map[interface{}]interface{}); ok {
pkiMap["ca"] = "ca.crt"
pkiMap["cert"] = name + ".crt"
pkiMap["key"] = name + ".key"
} else {
return "", errors.New("pki not found in nebula.yml")
}
// export configMap as YML
yamlData, err = yaml.Marshal(configMap)
if err != nil {
return "", err
}
return string(yamlData), nil
}
func getCApki() (string, error) {
// read config/ca.crt
caCrt, err := ioutil.ReadFile(utils.CONFIGFOLDER + "ca.crt")
if err != nil {
return "", err
}
return string(caCrt), nil
}
func killAllNebulaInstances() error {
processMux.Lock()
defer processMux.Unlock()
cmd := exec.Command("ps", "-e", "-o", "pid,command")
output, err := cmd.CombinedOutput()
if err != nil {
return err
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, binaryToRun()) {
fields := strings.Fields(line)
if len(fields) > 1 {
pid := fields[0]
pidInt, _ := strconv.Atoi(pid)
process, err := os.FindProcess(pidInt)
if err != nil {
return err
}
err = process.Kill()
if err != nil {
return err
}
utils.Log(fmt.Sprintf("Killed Nebula instance with PID %s\n", pid))
}
}
}
return nil
}
func generateNebulaCert(name, ip string, saveToFile bool) (string, string, error) {
// Run the nebula-cert command
cmd := exec.Command(binaryToRun() + "-cert",
"sign",
"-ca-crt", utils.CONFIGFOLDER + "ca.crt",
"-ca-key", utils.CONFIGFOLDER + "ca.key",
"-name", name,
"-ip", ip,
)
utils.Debug(cmd.String())
cmd.Stderr = os.Stderr
if utils.LoggingLevelLabels[utils.GetMainConfig().LoggingLevel] == utils.DEBUG {
cmd.Stdout = os.Stdout
} else {
cmd.Stdout = nil
}
cmd.Run()
if cmd.ProcessState.ExitCode() != 0 {
return "", "", fmt.Errorf("nebula-cert exited with an error, check the Cosmos logs")
}
// Read the generated certificate and key files
certPath := fmt.Sprintf("./%s.crt", name)
keyPath := fmt.Sprintf("./%s.key", name)
utils.Debug("Reading certificate from " + certPath)
utils.Debug("Reading key from " + keyPath)
certContent, errCert := ioutil.ReadFile(certPath)
if errCert != nil {
return "", "", fmt.Errorf("failed to read certificate file: %s", errCert)
}
keyContent, errKey := ioutil.ReadFile(keyPath)
if errKey != nil {
return "", "", fmt.Errorf("failed to read key file: %s", errKey)
}
if saveToFile {
cmd = exec.Command("mv", certPath, utils.CONFIGFOLDER + name + ".crt")
utils.Debug(cmd.String())
cmd.Run()
cmd = exec.Command("mv", keyPath, utils.CONFIGFOLDER + name + ".key")
utils.Debug(cmd.String())
cmd.Run()
} else {
// Delete the generated certificate and key files
if err := os.Remove(certPath); err != nil {
return "", "", fmt.Errorf("failed to delete certificate file: %s", err)
}
if err := os.Remove(keyPath); err != nil {
return "", "", fmt.Errorf("failed to delete key file: %s", err)
}
}
return string(certContent), string(keyContent), nil
}
func generateNebulaCACert(name string) (error) {
// Run the nebula-cert command to generate CA certificate and key
cmd := exec.Command(binaryToRun() + "-cert", "ca", "-name", "\""+name+"\"")
utils.Debug(cmd.String())
cmd.Stderr = os.Stderr
if utils.LoggingLevelLabels[utils.GetMainConfig().LoggingLevel] == utils.DEBUG {
cmd.Stdout = os.Stdout
} else {
cmd.Stdout = nil
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("nebula-cert error: %s", err)
}
// copy to /config/ca.*
cmd = exec.Command("mv", "./ca.crt", utils.CONFIGFOLDER + "ca.crt")
cmd.Run()
cmd = exec.Command("mv", "./ca.key", utils.CONFIGFOLDER + "ca.key")
cmd.Run()
return nil
}

View File

@ -0,0 +1,95 @@
package constellation
import (
"github.com/azukaar/cosmos-server/src/utils"
)
var NebulaDefaultConfig utils.NebulaConfig
func InitConfig() {
NebulaDefaultConfig = utils.NebulaConfig {
PKI: struct {
CA string `yaml:"ca"`
Cert string `yaml:"cert"`
Key string `yaml:"key"`
}{
CA: utils.CONFIGFOLDER + "ca.crt",
Cert: utils.CONFIGFOLDER + "cosmos.crt",
Key: utils.CONFIGFOLDER + "cosmos.key",
},
StaticHostMap: map[string][]string{
},
Lighthouse: struct {
AMLighthouse bool `yaml:"am_lighthouse"`
Interval int `yaml:"interval"`
Hosts []string `yaml:"hosts"`
}{
AMLighthouse: true,
Interval: 60,
Hosts: []string{},
},
Listen: struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
}{
Host: "0.0.0.0",
Port: 4242,
},
Punchy: struct {
Punch bool `yaml:"punch"`
}{
Punch: true,
},
Relay: struct {
AMRelay bool `yaml:"am_relay"`
UseRelays bool `yaml:"use_relays"`
}{
AMRelay: false,
UseRelays: true,
},
TUN: struct {
Disabled bool `yaml:"disabled"`
Dev string `yaml:"dev"`
DropLocalBroadcast bool `yaml:"drop_local_broadcast"`
DropMulticast bool `yaml:"drop_multicast"`
TxQueue int `yaml:"tx_queue"`
MTU int `yaml:"mtu"`
Routes []string `yaml:"routes"`
UnsafeRoutes []string `yaml:"unsafe_routes"`
}{
Disabled: false,
Dev: "nebula1",
DropLocalBroadcast: false,
DropMulticast: false,
TxQueue: 500,
MTU: 1300,
Routes: nil,
UnsafeRoutes: nil,
},
Logging: struct {
Level string `yaml:"level"`
Format string `yaml:"format"`
}{
Level: "info",
Format: "text",
},
Firewall: struct {
OutboundAction string `yaml:"outbound_action"`
InboundAction string `yaml:"inbound_action"`
Conntrack utils.NebulaConntrackConfig `yaml:"conntrack"`
Outbound []utils.NebulaFirewallRule `yaml:"outbound"`
Inbound []utils.NebulaFirewallRule `yaml:"inbound"`
}{
OutboundAction: "drop",
InboundAction: "drop",
Conntrack: utils.NebulaConntrackConfig{
TCPTimeout: "12m",
UDPTimeout: "3m",
DefaultTimeout: "10m",
},
Outbound: nil,
Inbound: nil,
},
}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/azukaar/cosmos-server/src/docker"
"github.com/azukaar/cosmos-server/src/authorizationserver"
"github.com/azukaar/cosmos-server/src/market"
"github.com/azukaar/cosmos-server/src/constellation"
"github.com/gorilla/mux"
"strconv"
"time"
@ -331,6 +332,7 @@ func InitServer() *mux.Router {
srapi.HandleFunc("/api/background", UploadBackground)
srapi.HandleFunc("/api/background/{ext}", GetBackground)
srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
srapi.Use(utils.EnsureHostname)

View File

@ -9,6 +9,7 @@ import (
"github.com/azukaar/cosmos-server/src/utils"
"github.com/azukaar/cosmos-server/src/authorizationserver"
"github.com/azukaar/cosmos-server/src/market"
"github.com/azukaar/cosmos-server/src/constellation"
)
func main() {
@ -44,5 +45,7 @@ func main() {
authorizationserver.Init()
constellation.Init()
StartServer()
}

View File

@ -2,12 +2,9 @@ package user
import (
"net/http"
// "io"
// "os"
"encoding/json"
"go.mongodb.org/mongo-driver/mongo"
"time"
// "golang.org/x/crypto/bcrypt"
"github.com/azukaar/cosmos-server/src/utils"
)

View File

@ -181,20 +181,20 @@ func DoLetsEncrypt() (string, string) {
}
err = client.Challenge.SetDNS01Provider(provider)
}
} else {
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", config.HTTPConfig.HTTPPort))
if err != nil {
Error("LETSENCRYPT_HTTP01", err)
LetsEncryptErrors = append(LetsEncryptErrors, err.Error())
return "", ""
}
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", config.HTTPConfig.HTTPPort))
if err != nil {
Error("LETSENCRYPT_HTTP01", err)
LetsEncryptErrors = append(LetsEncryptErrors, err.Error())
return "", ""
}
err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", config.HTTPConfig.HTTPSPort))
if err != nil {
Error("LETSENCRYPT_TLS01", err)
LetsEncryptErrors = append(LetsEncryptErrors, err.Error())
return "", ""
err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", config.HTTPConfig.HTTPSPort))
if err != nil {
Error("LETSENCRYPT_TLS01", err)
LetsEncryptErrors = append(LetsEncryptErrors, err.Error())
return "", ""
}
}
// New users will need to register

View File

@ -90,6 +90,7 @@ type Config struct {
MarketConfig MarketConfig
HomepageConfig HomepageConfig
ThemeConfig ThemeConfig
ConstellationConfig ConstellationConfig
}
type HomepageConfig struct {
@ -205,4 +206,84 @@ type MarketConfig struct {
type MarketSource struct {
Name string
Url string
}
}
type ConstellationConfig struct {
Enabled bool
NebulaConfig NebulaConfig
}
type NebulaFirewallRule struct {
Port string `yaml:"port"`
Proto string `yaml:"proto"`
Host string `yaml:"host"`
Groups []string `yaml:"groups,omitempty"omitempty"`
}
type NebulaConntrackConfig struct {
TCPTimeout string `yaml:"tcp_timeout"`
UDPTimeout string `yaml:"udp_timeout"`
DefaultTimeout string `yaml:"default_timeout"`
}
type NebulaConfig struct {
PKI struct {
CA string `yaml:"ca"`
Cert string `yaml:"cert"`
Key string `yaml:"key"`
} `yaml:"pki"`
StaticHostMap map[string][]string `yaml:"static_host_map"`
Lighthouse struct {
AMLighthouse bool `yaml:"am_lighthouse"`
Interval int `yaml:"interval"`
Hosts []string `yaml:"hosts"`
} `yaml:"lighthouse"`
Listen struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
} `yaml:"listen"`
Punchy struct {
Punch bool `yaml:"punch"`
} `yaml:"punchy"`
Relay struct {
AMRelay bool `yaml:"am_relay"`
UseRelays bool `yaml:"use_relays"`
} `yaml:"relay"`
TUN struct {
Disabled bool `yaml:"disabled"`
Dev string `yaml:"dev"`
DropLocalBroadcast bool `yaml:"drop_local_broadcast"`
DropMulticast bool `yaml:"drop_multicast"`
TxQueue int `yaml:"tx_queue"`
MTU int `yaml:"mtu"`
Routes []string `yaml:"routes"`
UnsafeRoutes []string `yaml:"unsafe_routes"`
} `yaml:"tun"`
Logging struct {
Level string `yaml:"level"`
Format string `yaml:"format"`
} `yaml:"logging"`
Firewall struct {
OutboundAction string `yaml:"outbound_action"`
InboundAction string `yaml:"inbound_action"`
Conntrack NebulaConntrackConfig `yaml:"conntrack"`
Outbound []NebulaFirewallRule `yaml:"outbound"`
Inbound []NebulaFirewallRule `yaml:"inbound"`
} `yaml:"firewall"`
}
type Device struct {
DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"`
Nickname string `json:"nickname",validate:"required,min=3,max=32,alphanum"`
PublicKey string `json:"publicKey",omitempty`
PrivateKey string `json:"privateKey",omitempty`
IP string `json:"ip",validate:"required,ipv4"`
}

View File

@ -37,6 +37,8 @@ var ReBootstrapContainer func(string) error
var LetsEncryptErrors = []string{}
var CONFIGFOLDER = "/config/"
var DefaultConfig = Config{
LoggingLevel: "INFO",
NewInstall: true,
@ -193,6 +195,10 @@ func LoadBaseMainConfig(config Config) {
if os.Getenv("COSMOS_SERVER_COUNTRY") != "" {
MainConfig.ServerCountry = os.Getenv("COSMOS_SERVER_COUNTRY")
}
if os.Getenv("COSMOS_CONFIG_FOLDER") != "" {
Log("Overwriting config folder with " + os.Getenv("COSMOS_CONFIG_FOLDER"))
CONFIGFOLDER = os.Getenv("COSMOS_CONFIG_FOLDER")
}
if MainConfig.DockerConfig.DefaultDataPath == "" {
MainConfig.DockerConfig.DefaultDataPath = "/usr"
@ -219,7 +225,7 @@ func GetConfigFileName() string {
configFile := os.Getenv("CONFIG_FILE")
if configFile == "" {
configFile = "/config/cosmos.config.json"
configFile = CONFIGFOLDER + "cosmos.config.json"
}
return configFile