Merge branch 'unstable'
This commit is contained in:
commit
f796c521ef
|
@ -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
|
||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -12,4 +12,10 @@ todo.txt
|
|||
LICENCE
|
||||
tokens.json
|
||||
.vscode
|
||||
GeoLite2-Country.mmdb
|
||||
GeoLite2-Country.mmdb
|
||||
dns-blacklist.txt
|
||||
zz_test_config
|
||||
nebula-arm
|
||||
nebula-arm-cert
|
||||
nebula
|
||||
nebula-cert
|
66
LICENCE
66
LICENCE
|
@ -1,31 +1,57 @@
|
|||
“Commons Clause” License Condition v1.0
|
||||
|
||||
The Software is provided to you by the Licensor under the
|
||||
License, as defined below, subject to the following condition.
|
||||
|
||||
Without limiting other conditions in the License, the grant
|
||||
of rights under the License will not include, and the License
|
||||
does not grant to you, the right to Sell the Software.
|
||||
|
||||
For purposes of the foregoing, “Sell” means practicing any or
|
||||
all of the rights granted to you under the License to provide
|
||||
to third parties, for a fee or other consideration (including
|
||||
without limitation fees for hosting or consulting/ support
|
||||
services related to the Software), a product or service whose
|
||||
value derives, entirely or substantially, from the functionality
|
||||
of the Software. Any license notice or attribution required by
|
||||
the License must also include this Commons Clause License
|
||||
Condition notice.
|
||||
|
||||
Software: Cosmos-Server
|
||||
|
||||
License: Apache 2.0 with Commons Clause
|
||||
License: Apache 2.0 with Commons Clause and Anti Tampering Clause
|
||||
|
||||
Licensor: Yann Stepienik
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
||||
“Commons Clause” License Condition v1.0
|
||||
|
||||
The Software is provided to you by the Licensor under the
|
||||
License, as defined below, subject to the following condition.
|
||||
|
||||
Without limiting other conditions in the License, the grant
|
||||
of rights under the License will not include, and the License
|
||||
does not grant to you, the right to Sell the Software.
|
||||
|
||||
For purposes of the foregoing, “Sell” means practicing any or
|
||||
all of the rights granted to you under the License to provide
|
||||
to third parties, for a fee or other consideration (including
|
||||
without limitation fees for hosting or consulting/ support
|
||||
services related to the Software), a product or service whose
|
||||
value derives, entirely or substantially, from the functionality
|
||||
of the Software. Any license notice or attribution required by
|
||||
the License must also include this Commons Clause License
|
||||
Condition notice.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
||||
"Anti Tampering Clause” License Condition v1.0
|
||||
|
||||
Notwithstanding any provision of the Apache License 2.0, if the User
|
||||
(or any party receiving or distributing derivative works, services,
|
||||
or anything of value from the User related to the Software), directly
|
||||
or indirectly, seeks to tamper with, alter, circumvent, or avoid
|
||||
compliance with any subscription, paywall, feature restriction, or any
|
||||
other licensing mechanism built into the Software or its usage, the
|
||||
License granted under the Apache License 2.0 shall automatically and
|
||||
immediately terminate, and access to the Software shall be withdrawn
|
||||
with immediate effect. Upon such termination, any and all rights
|
||||
established under the Apache License 2.0 shall be null and void.
|
||||
|
||||
Tampering includes but is not limited to: (a) removing, disabling,
|
||||
or circumventing any license key or other copy protection mechanism,
|
||||
(b) redistributing parts or all of a feature that was intended
|
||||
to be a paid feature, without keeping the restrictions, limitations,
|
||||
or other licensing mechanisms with it(c) disabling, circumventing, or
|
||||
avoiding any feature of the Software that is intended to enforce usage or
|
||||
copy restrictions, or (d) providing or distributing any information
|
||||
or code that enables disabling, circumvention, or avoidance of any
|
||||
feature of the Software that is intended to enforce usage or copy
|
||||
restrictions.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
|
1
build.sh
1
build.sh
|
@ -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
|
||||
|
|
16
changelog.md
16
changelog.md
|
@ -1,3 +1,19 @@
|
|||
<<<<<<< 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 [breaking change]
|
||||
- Rework headers for better compatibility
|
||||
- Improve experience for non-admin users
|
||||
- Fix bug with redirect on logout
|
||||
- Added OverwriteHostHeader to routes to override the host header sent to the target app
|
||||
- Added WhitelistInboundIPs to routes to filter incoming requests based on IP per URL
|
||||
|
||||
> **Note: If you use the ARM (:latest-arm) you need to manually update to using the :latest tag instead**
|
||||
|
||||
## Version 0.9.20 - 0.9.21
|
||||
- Add option to disable CORS hardening (with empty value)
|
||||
|
||||
|
|
2
cla.md
2
cla.md
|
@ -2,7 +2,7 @@ Cosmos Software Grant and Contributor License Agreement (“Agreement”)
|
|||
|
||||
This agreement is based on the Apache Software Foundation Contributor License Agreement. (v r190612)
|
||||
|
||||
Thank you for your interest in software projects stewarded by Raintank, Inc. dba Cosmos (“Cosmos”). In order to clarify the intellectual property license granted with Contributions from any person or entity, Cosmos must have a Contributor License Agreement (CLA) on file that has been agreed to by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of Cosmos and its users; it does not change your rights to use your own Contributions for any other purpose. This Agreement allows an individual to contribute to Cosmos on that individual’s own behalf, or an entity (the “Corporation”) to submit Contributions to Cosmos, to authorize Contributions submitted by its designated employees to Cosmos, and to grant copyright and patent licenses thereto.
|
||||
Thank you for your interest in dba Cosmos (“Cosmos”). In order to clarify the intellectual property license granted with Contributions from any person or entity, Cosmos must have a Contributor License Agreement (CLA) on file that has been agreed to by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of Cosmos and its users; it does not change your rights to use your own Contributions for any other purpose. This Agreement allows an individual to contribute to Cosmos on that individual’s own behalf, or an entity (the “Corporation”) to submit Contributions to Cosmos, to authorize Contributions submitted by its designated employees to Cosmos, and to grant copyright and patent licenses thereto.
|
||||
|
||||
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to Cosmos. Except for the license granted herein to Cosmos and recipients of software distributed by Cosmos, You reserve all right, title, and interest in and to Your Contributions.
|
||||
|
||||
|
|
131
client/src/api/constellation.demo.tsx
Normal file
131
client/src/api/constellation.demo.tsx
Normal file
|
@ -0,0 +1,131 @@
|
|||
import wrap from './wrap';
|
||||
|
||||
function list() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"data": [
|
||||
{
|
||||
"nickname": "admin",
|
||||
"deviceName": "phone",
|
||||
"publicKey": "-----BEGIN NEBULA X25519 PRIVATE KEY-----\naACf/...=\n-----END NEBULA X25519 PRIVATE KEY-----\n",
|
||||
"ip": "192.168.201.4/24",
|
||||
"isLighthouse": false,
|
||||
"isRelay": true,
|
||||
"publicHostname": "",
|
||||
"port": "4242",
|
||||
"blocked": false,
|
||||
"fingerprint": "..."
|
||||
},
|
||||
{
|
||||
"nickname": "admin",
|
||||
"deviceName": "laptop",
|
||||
"publicKey": "-----BEGIN NEBULA X25519 PRIVATE KEY-----\n78l4nDEB0+.../36YBQk7dkwg+.=\n-----END NEBULA X25519 PRIVATE KEY-----\n",
|
||||
"ip": "192.168.201.5/24",
|
||||
"isLighthouse": false,
|
||||
"isRelay": true,
|
||||
"publicHostname": "",
|
||||
"port": "4242",
|
||||
"blocked": false,
|
||||
"fingerprint": "..."
|
||||
},
|
||||
{
|
||||
"nickname": "Martha",
|
||||
"deviceName": "pink phone",
|
||||
"publicKey": "-----BEGIN NEBULA X25519 PRIVATE KEY-----\naACf/..=\n-----END NEBULA X25519 PRIVATE KEY-----\n",
|
||||
"ip": "192.168.201.6/24",
|
||||
"isLighthouse": false,
|
||||
"isRelay": true,
|
||||
"publicHostname": "",
|
||||
"port": "4242",
|
||||
"blocked": false,
|
||||
"fingerprint": "..."
|
||||
}
|
||||
],
|
||||
"status": "OK"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function addDevice(device) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"data": {
|
||||
"CA": "-----BEGIN NEBULA CERTIFICATE-----\....\n+dfE+ikL8jUh/n+C+....\....\nZon/Dw==\n-----END NEBULA CERTIFICATE-----\n",
|
||||
"Config": "constellation_api_key: ...\nconstellation_device_name: test\nconstellation_local_dns_overwrite: true\nconstellation_local_dns_overwrite_address: 192.168.201.1\nconstellation_public_hostname: \"\"\nfirewall:\n conntrack:\n default_timeout: 10m\n tcp_timeout: 12m\n udp_timeout: 3m\n inbound:\n - host: any\n port: any\n proto: any\n inbound_action: drop\n outbound:\n - host: any\n port: any\n proto: any\n outbound_action: drop\nlighthouse:\n am_lighthouse: false\n hosts:\n - 192.168.201.1\n interval: 60\nlisten:\n host: 0.0.0.0\n port: \"4242\"\nlogging:\n format: text\n level: info\npki:\n blocklist: []\n ca: |\n -----BEGIN NEBULA CERTIFICATE-----\n ...\n +dfE+ikL8jUh/n+C+...\n .\n Zon/Dw==\n -----END NEBULA CERTIFICATE-----\n cert: |\n -----BEGIN NEBULA CERTIFICATE-----\n CmIKBHRlc3QSCoeSo4UMgP7//..\n ...+QwZSiBxLdKhjkCH+.../..\n ./hfL+....\n ..==\n -----END NEBULA CERTIFICATE-----\n key: |\n -----BEGIN NEBULA X25519 PRIVATE KEY-----\n nS39dWX7uo1rhTvP2yl2XonGx3fWEkpk+43thNrMu7U=\n -----END NEBULA X25519 PRIVATE KEY-----\npunchy:\n punch: true\n respond: true\nrelay:\n am_relay: false\n relays:\n - 192.168.201.1\n use_relays: true\nstatic_host_map:\n 192.168.201.1:\n - vpn.domain.com:4242\ntun:\n dev: nebula1\n disabled: false\n drop_local_broadcast: false\n drop_multicast: false\n mtu: 1300\n routes: []\n tx_queue: 500\n unsafe_routes: []\n",
|
||||
"DeviceName": "test",
|
||||
"IP": "192.168.201.7/24",
|
||||
"IsLighthouse": false,
|
||||
"IsRelay": true,
|
||||
"LighthousesList": [],
|
||||
"Nickname": "admin",
|
||||
"Port": "4242",
|
||||
"PrivateKey": "-----BEGIN NEBULA CERTIFICATE-----\...//w8o3ZaFqQYwhdGFuAY6IGXmYRCr3z932Y....w\..==\n-----END NEBULA CERTIFICATE-----\n",
|
||||
"PublicHostname": "",
|
||||
"PublicKey": "-----BEGIN NEBULA X25519 PRIVATE KEY-----\nnS39dWX...hTvP......+43thNrMu7U=\n-----END NEBULA X25519 PRIVATE KEY-----\n"
|
||||
},
|
||||
"status": "OK"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function restart() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"status": "ok",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function reset() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"status": "ok",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"data": "pki:\n ca: /config/ca.crt\n cert: /config/cosmos.crt\n key: /config/cosmos.key\n blocklist: []\nstatic_host_map:\n 192.168.201.1:\n - vpn.domain.com:4242\nlighthouse:\n am_lighthouse: true\n interval: 60\n hosts: []\nlisten:\n host: 0.0.0.0\n port: 4242\npunchy:\n punch: true\n respond: true\nrelay:\n am_relay: true\n use_relays: true\n relays: []\ntun:\n disabled: false\n dev: nebula1\n drop_local_broadcast: false\n drop_multicast: false\n tx_queue: 500\n mtu: 1300\n routes: []\n unsafe_routes: []\nlogging:\n level: info\n format: text\nfirewall:\n outbound_action: drop\n inbound_action: drop\n conntrack:\n tcp_timeout: 12m\n udp_timeout: 3m\n default_timeout: 10m\n outbound:\n - port: any\n proto: any\n host: any\n inbound:\n - port: any\n proto: any\n host: any\n",
|
||||
"status": "OK"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function getLogs() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"data": "Some logs...",
|
||||
"status": "OK"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function connect(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"status": "ok",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function block(nickname, devicename, block) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"status": "ok",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
list,
|
||||
addDevice,
|
||||
restart,
|
||||
getConfig,
|
||||
getLogs,
|
||||
reset,
|
||||
connect,
|
||||
block,
|
||||
};
|
110
client/src/api/constellation.tsx
Normal file
110
client/src/api/constellation.tsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
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),
|
||||
}))
|
||||
}
|
||||
|
||||
function restart() {
|
||||
return wrap(fetch('/cosmos/api/constellation/restart', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
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',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
function getLogs() {
|
||||
return wrap(fetch('/cosmos/api/constellation/logs', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
function connect(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
fetch('/cosmos/api/constellation/connect', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: reader.result,
|
||||
})
|
||||
.then(response => {
|
||||
// Add additional response handling here if needed.
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle the error.
|
||||
reject(error);
|
||||
});
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
reject(new Error('Failed to read the file.'));
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
function block(nickname, devicename, block) {
|
||||
return wrap(fetch(`/cosmos/api/constellation/block`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nickname, devicename, block
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
export {
|
||||
list,
|
||||
addDevice,
|
||||
restart,
|
||||
getConfig,
|
||||
getLogs,
|
||||
reset,
|
||||
connect,
|
||||
block,
|
||||
};
|
|
@ -361,7 +361,74 @@
|
|||
],
|
||||
"ServerCountry": "",
|
||||
"RequireMFA": false,
|
||||
"AutoUpdate": false
|
||||
"AutoUpdate": false,
|
||||
"ConstellationConfig": {
|
||||
"Enabled": true,
|
||||
"SlaveMode": false,
|
||||
"PrivateNode": false,
|
||||
"DNSDisabled": false,
|
||||
"DNSPort": "",
|
||||
"DNSFallback": "8.8.8.8:53",
|
||||
"DNSBlockBlacklist": true,
|
||||
"DNSAdditionalBlocklists": [
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt"
|
||||
],
|
||||
"CustomDNSEntries": [],
|
||||
"NebulaConfig": {
|
||||
"PKI": {
|
||||
"CA": "",
|
||||
"Cert": "",
|
||||
"Key": "",
|
||||
"Blocklist": null
|
||||
},
|
||||
"StaticHostMap": null,
|
||||
"Lighthouse": {
|
||||
"AMLighthouse": false,
|
||||
"Interval": 0,
|
||||
"Hosts": null
|
||||
},
|
||||
"Listen": {
|
||||
"Host": "",
|
||||
"Port": 0
|
||||
},
|
||||
"Punchy": {
|
||||
"Punch": false,
|
||||
"Respond": false
|
||||
},
|
||||
"Relay": {
|
||||
"AMRelay": true,
|
||||
"UseRelays": false,
|
||||
"Relays": null
|
||||
},
|
||||
"TUN": {
|
||||
"Disabled": false,
|
||||
"Dev": "",
|
||||
"DropLocalBroadcast": false,
|
||||
"DropMulticast": false,
|
||||
"TxQueue": 0,
|
||||
"MTU": 0,
|
||||
"Routes": null,
|
||||
"UnsafeRoutes": null
|
||||
},
|
||||
"Logging": {
|
||||
"Level": "",
|
||||
"Format": ""
|
||||
},
|
||||
"Firewall": {
|
||||
"OutboundAction": "",
|
||||
"InboundAction": "",
|
||||
"Conntrack": {
|
||||
"TCPTimeout": "",
|
||||
"UDPTimeout": "",
|
||||
"DefaultTimeout": ""
|
||||
},
|
||||
"Outbound": null,
|
||||
"Inbound": null
|
||||
}
|
||||
},
|
||||
"ConstellationHostname": "vpn.domain.com"
|
||||
}
|
||||
},
|
||||
"updates": {
|
||||
"/Sonarr": true,
|
||||
|
|
28
client/src/api/downloadButton.jsx
Normal file
28
client/src/api/downloadButton.jsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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';
|
||||
|
@ -10,6 +11,7 @@ import * as configDemo from './config.demo';
|
|||
import * as dockerDemo from './docker.demo';
|
||||
import * as indexDemo from './index.demo';
|
||||
import * as marketDemo from './market.demo';
|
||||
import * as constellationDemo from './constellation.demo';
|
||||
|
||||
import wrap from './wrap';
|
||||
import { redirectToLocal } from '../utils/indexs';
|
||||
|
@ -211,6 +213,7 @@ let users = _users;
|
|||
let config = _config;
|
||||
let docker = _docker;
|
||||
let market = _market;
|
||||
let constellation = _constellation;
|
||||
|
||||
if(isDemo) {
|
||||
auth = authDemo;
|
||||
|
@ -224,6 +227,7 @@ if(isDemo) {
|
|||
checkHost = indexDemo.checkHost;
|
||||
getDNS = indexDemo.getDNS;
|
||||
uploadBackground = indexDemo.uploadBackground;
|
||||
constellation = constellationDemo;
|
||||
}
|
||||
|
||||
export {
|
||||
|
@ -232,6 +236,7 @@ export {
|
|||
config,
|
||||
docker,
|
||||
market,
|
||||
constellation,
|
||||
getStatus,
|
||||
newInstall,
|
||||
isOnline,
|
||||
|
|
File diff suppressed because it is too large
Load diff
BIN
client/src/assets/images/icons/constellation.png
Normal file
BIN
client/src/assets/images/icons/constellation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
94
client/src/components/apiModal.jsx
Normal file
94
client/src/components/apiModal.jsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
// 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 preStyle = {
|
||||
backgroundColor: '#000',
|
||||
color: '#fff',
|
||||
padding: '10px',
|
||||
borderRadius: '5px',
|
||||
overflow: 'auto',
|
||||
maxHeight: '500px',
|
||||
maxWidth: '100%',
|
||||
width: '100%',
|
||||
margin: '0',
|
||||
position: 'relative',
|
||||
fontSize: '12px',
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordWrap: 'break-word',
|
||||
wordBreak: 'break-all',
|
||||
lineHeight: '1.5',
|
||||
boxShadow: '0 0 10px rgba(0,0,0,0.5)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
boxSizing: 'border-box',
|
||||
marginBottom: '10px',
|
||||
marginTop: '10px',
|
||||
marginLeft: '0',
|
||||
marginRight: '0',
|
||||
display: 'block',
|
||||
textAlign: 'left',
|
||||
verticalAlign: 'baseline',
|
||||
opacity: '1',
|
||||
}
|
||||
|
||||
const ApiModal = ({ callback, label }) => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [content, setContent] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const getContent = async () => {
|
||||
setLoading(true);
|
||||
let content = await callback();
|
||||
setContent(content.data);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (openModal)
|
||||
getContent();
|
||||
}, [openModal]);
|
||||
|
||||
return <>
|
||||
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<DialogTitle>Refresh Page</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
<pre style={preStyle}>
|
||||
{content}
|
||||
</pre>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
getContent();
|
||||
}}>Refresh</LoadingButton>
|
||||
<Button onClick={() => {
|
||||
setOpenModal(false);
|
||||
}}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Button
|
||||
disableElevation
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setOpenModal(true);
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</>
|
||||
};
|
||||
|
||||
export default ApiModal;
|
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>
|
||||
|
|
|
@ -123,8 +123,9 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.loading-image {
|
||||
background: url('/assets/images/icons/cosmos_gray.png') no-repeat center center;
|
||||
.loading-image:empty {
|
||||
/* background: url('assets/images/icons/cosmos_gray.png') no-repeat center center;
|
||||
background-size: contain; */
|
||||
}
|
||||
|
||||
.raw-table table {
|
||||
|
|
|
@ -47,7 +47,7 @@ const NavGroup = ({ item }) => {
|
|||
}
|
||||
sx={{ mb: drawerOpen ? 1.5 : 0, py: 0, zIndex: 0 }}
|
||||
>
|
||||
{navCollapse}
|
||||
{navCollapse}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography }
|
|||
|
||||
// project import
|
||||
import { activeItem } from '../../../../../store/reducers/menu';
|
||||
import { useClientInfos } from '../../../../../utils/hooks';
|
||||
|
||||
// ==============================|| NAVIGATION - LIST ITEM ||============================== //
|
||||
|
||||
|
@ -17,6 +18,12 @@ const NavItem = ({ item, level }) => {
|
|||
const dispatch = useDispatch();
|
||||
const menu = useSelector((state) => state.menu);
|
||||
const { drawerOpen, openItem } = menu;
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
if (item.adminOnly && !isAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let itemTarget = '_self';
|
||||
if (item.target) {
|
||||
|
@ -54,6 +61,16 @@ const NavItem = ({ item, level }) => {
|
|||
const textColor = 'text.primary';
|
||||
const iconSelectedColor = 'primary.main';
|
||||
|
||||
// SET BETA (TODO REMOVE)
|
||||
if(item.title === "Constellation")
|
||||
item.title = <>{item.title} <span style={{
|
||||
color: 'gray',
|
||||
fontSize: '11px',
|
||||
textDecoration: 'italic',
|
||||
transform: 'translateY(-5px)',
|
||||
display: 'inline-block',
|
||||
}}>Beta</span></>;
|
||||
|
||||
return (
|
||||
<ListItemButton
|
||||
{...listItemProps}
|
||||
|
|
|
@ -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 = {
|
||||
|
@ -20,7 +20,8 @@ const pages = {
|
|||
title: 'ServApps',
|
||||
type: 'item',
|
||||
url: '/cosmos-ui/servapps',
|
||||
icon: AppstoreOutlined
|
||||
icon: AppstoreOutlined,
|
||||
adminOnly: true
|
||||
},
|
||||
{
|
||||
id: 'url',
|
||||
|
@ -29,12 +30,21 @@ 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',
|
||||
type: 'item',
|
||||
url: '/cosmos-ui/config-users',
|
||||
icon: icons.ProfileOutlined,
|
||||
adminOnly: true
|
||||
},
|
||||
{
|
||||
id: 'openid',
|
||||
|
@ -42,6 +52,7 @@ const pages = {
|
|||
type: 'item',
|
||||
url: '/cosmos-ui/openid-manage',
|
||||
icon: PicLeftOutlined,
|
||||
adminOnly: true
|
||||
},
|
||||
{
|
||||
id: 'config',
|
||||
|
|
|
@ -9,7 +9,7 @@ import AuthWrapper from './AuthWrapper';
|
|||
import { useEffect } from 'react';
|
||||
|
||||
import * as API from '../../api';
|
||||
import { redirectTo } from '../../utils/indexs';
|
||||
import { redirectTo, redirectToLocal } from '../../utils/indexs';
|
||||
|
||||
// ================================|| REGISTER ||================================ //
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
FormHelperText,
|
||||
} from '@mui/material';
|
||||
import RestartModal from '../users/restart';
|
||||
import { CosmosCheckbox, CosmosFormDivider, CosmosInputText, CosmosSelect } from '../users/formShortcuts';
|
||||
import { CosmosCheckbox, CosmosCollapse, CosmosFormDivider, CosmosInputText, CosmosSelect } from '../users/formShortcuts';
|
||||
import { CosmosContainerPicker } from '../users/containerPicker';
|
||||
import { snackit } from '../../../api/wrap';
|
||||
import { ValidateRouteSchema, sanitizeRoute } from '../../../utils/routes';
|
||||
|
@ -72,12 +72,20 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC
|
|||
StripPathPrefix: routeConfig.StripPathPrefix,
|
||||
AuthEnabled: routeConfig.AuthEnabled,
|
||||
_SmartShield_Enabled: (routeConfig.SmartShield ? routeConfig.SmartShield.Enabled : false),
|
||||
RestrictToConstellation: routeConfig.RestrictToConstellation,
|
||||
OverwriteHostHeader: routeConfig.OverwriteHostHeader,
|
||||
WhitelistInboundIPs: routeConfig.WhitelistInboundIPs && routeConfig.WhitelistInboundIPs.join(', '),
|
||||
}}
|
||||
validationSchema={ValidateRouteSchema}
|
||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
if(!submitButton) {
|
||||
return false;
|
||||
} else {
|
||||
let commaSepIps = values.WhitelistInboundIPs;
|
||||
if(commaSepIps) {
|
||||
values.WhitelistInboundIPs = commaSepIps.split(',').map((ip) => ip.trim());
|
||||
}
|
||||
|
||||
let fullValues = {
|
||||
...routeConfig,
|
||||
...values,
|
||||
|
@ -256,6 +264,37 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC
|
|||
label="Smart Shield Protection"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosCheckbox
|
||||
name="RestrictToConstellation"
|
||||
label="Restrict access to Constellation VPN"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosCollapse title={'Advanced Settings'}>
|
||||
<Stack spacing={2}>
|
||||
<Alert severity='info'>These settings are for advanced users only. Please do not change these unless you know what you are doing.</Alert>
|
||||
<CosmosInputText
|
||||
name="OverwriteHostHeader"
|
||||
label="Overwrite Host Header (use this to chain resolve request from another server/ip)"
|
||||
placeholder="Overwrite Host Header"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<Alert severity='warning'>
|
||||
This setting will filter out all requests that do not come from the specified IPs.
|
||||
This requires your setup to report the true IP of the client. By default it will, but some exotic setup (like installing docker/cosmos on Windows, or behind Cloudlfare)
|
||||
will prevent Cosmos from knowing what is the client's real IP. If you used "Restrict to Constellation" above, Constellation IPs will always be allowed regardless of this setting.
|
||||
</Alert>
|
||||
|
||||
<CosmosInputText
|
||||
name="WhitelistInboundIPs"
|
||||
label="Whitelist Inbound IPs and/or IP ranges (comma separated)"
|
||||
placeholder="Whitelist Inbound IPs"
|
||||
formik={formik}
|
||||
/>
|
||||
</Stack>
|
||||
</CosmosCollapse>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
{submitButton && <MainCard ><Button
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import MainCard from '../../../components/MainCard';
|
||||
import RestartModal from '../users/restart';
|
||||
import { Chip, Divider, Stack, useMediaQuery } from '@mui/material';
|
||||
import { Checkbox, Chip, Divider, FormControlLabel, Stack, useMediaQuery } from '@mui/material';
|
||||
import HostChip from '../../../components/hostChip';
|
||||
import { RouteMode, RouteSecurity } from '../../../components/routeComponents';
|
||||
import { getFaviconURL } from '../../../utils/routes';
|
||||
|
@ -9,6 +9,8 @@ import * as API from '../../../api';
|
|||
import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
|
||||
import IsLoggedIn from '../../../isLoggedIn';
|
||||
import { redirectToLocal } from '../../../utils/indexs';
|
||||
import { CosmosCheckbox } from '../users/formShortcuts';
|
||||
import { Field } from 'formik';
|
||||
|
||||
const info = {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
||||
|
|
|
@ -31,6 +31,7 @@ import { TwitterPicker
|
|||
|
||||
// TODO: Remove circular deps
|
||||
import {SetPrimaryColor, SetSecondaryColor} from '../../../App';
|
||||
import { useClientInfos } from '../../../utils/hooks';
|
||||
|
||||
const ConfigManagement = () => {
|
||||
const [config, setConfig] = React.useState(null);
|
||||
|
@ -38,6 +39,8 @@ const ConfigManagement = () => {
|
|||
const [openResartModal, setOpenRestartModal] = React.useState(false);
|
||||
const [uploadingBackground, setUploadingBackground] = React.useState(false);
|
||||
const [saveLabel, setSaveLabel] = React.useState("Save");
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
function refresh() {
|
||||
API.config.get().then((res) => {
|
||||
|
@ -62,9 +65,9 @@ const ConfigManagement = () => {
|
|||
refresh();
|
||||
}}>Refresh</Button>
|
||||
|
||||
<Button variant="outlined" color="primary" startIcon={<SyncOutlined />} onClick={() => {
|
||||
{isAdmin && <Button variant="outlined" color="primary" startIcon={<SyncOutlined />} onClick={() => {
|
||||
setOpenRestartModal(true);
|
||||
}}>Restart Server</Button>
|
||||
}}>Restart Server</Button>}
|
||||
</Stack>
|
||||
|
||||
{config && <>
|
||||
|
@ -186,7 +189,7 @@ const ConfigManagement = () => {
|
|||
{(formik) => (
|
||||
<form noValidate onSubmit={formik.handleSubmit}>
|
||||
<Stack spacing={3}>
|
||||
<MainCard>
|
||||
{isAdmin && <MainCard>
|
||||
{formik.errors.submit && (
|
||||
<Grid item xs={12}>
|
||||
<FormHelperText error>{formik.errors.submit}</FormHelperText>
|
||||
|
@ -205,7 +208,13 @@ const ConfigManagement = () => {
|
|||
{saveLabel}
|
||||
</LoadingButton>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</MainCard>}
|
||||
|
||||
{!isAdmin && <div>
|
||||
<Alert severity="warning">As you are not an admin, you can't edit the configuration.
|
||||
This page is only here for visibility.
|
||||
</Alert>
|
||||
</div>}
|
||||
|
||||
<MainCard title="General">
|
||||
<Grid container spacing={3}>
|
||||
|
@ -331,6 +340,29 @@ const ConfigManagement = () => {
|
|||
formik.setFieldValue('PrimaryColor', colorRGB);
|
||||
SetPrimaryColor(colorRGB);
|
||||
}}
|
||||
colors={[
|
||||
'#ab47bc',
|
||||
'#4527a0',
|
||||
'#FF6900',
|
||||
'#FCB900',
|
||||
'#7BDCB5',
|
||||
'#00D084',
|
||||
'#8ED1FC',
|
||||
'#0693E3',
|
||||
'#ABB8C3',
|
||||
'#EB144C',
|
||||
'#F78DA7',
|
||||
'#9900EF',
|
||||
'#FF0000',
|
||||
'#FFC0CB',
|
||||
'#20B2AA',
|
||||
'#FFFF00',
|
||||
'#8A2BE2',
|
||||
'#A52A2A',
|
||||
'#5F9EA0',
|
||||
'#7FFF00',
|
||||
'#D2691E'
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
@ -346,6 +378,29 @@ const ConfigManagement = () => {
|
|||
formik.setFieldValue('SecondaryColor', colorRGB);
|
||||
SetSecondaryColor(colorRGB);
|
||||
}}
|
||||
colors={[
|
||||
'#ab47bc',
|
||||
'#4527a0',
|
||||
'#FF6900',
|
||||
'#FCB900',
|
||||
'#7BDCB5',
|
||||
'#00D084',
|
||||
'#8ED1FC',
|
||||
'#0693E3',
|
||||
'#ABB8C3',
|
||||
'#EB144C',
|
||||
'#F78DA7',
|
||||
'#9900EF',
|
||||
'#FF0000',
|
||||
'#FFC0CB',
|
||||
'#20B2AA',
|
||||
'#FFFF00',
|
||||
'#8A2BE2',
|
||||
'#A52A2A',
|
||||
'#5F9EA0',
|
||||
'#7FFF00',
|
||||
'#D2691E'
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
@ -627,7 +682,7 @@ const ConfigManagement = () => {
|
|||
</Grid>
|
||||
</MainCard>
|
||||
|
||||
<MainCard>
|
||||
{isAdmin && <MainCard>
|
||||
{formik.errors.submit && (
|
||||
<Grid item xs={12}>
|
||||
<FormHelperText error>{formik.errors.submit}</FormHelperText>
|
||||
|
@ -646,7 +701,7 @@ const ConfigManagement = () => {
|
|||
{saveLabel}
|
||||
</LoadingButton>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</MainCard>}
|
||||
</Stack>
|
||||
</form>
|
||||
)}
|
||||
|
|
|
@ -27,26 +27,33 @@ import { strengthColor, strengthIndicator } from '../../../utils/password-streng
|
|||
|
||||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||
|
||||
export const CosmosInputText = ({ name, style, multiline, type, placeholder, onChange, label, formik }) => {
|
||||
export const CosmosInputText = ({ name, style, value, errors, multiline, type, placeholder, onChange, label, formik }) => {
|
||||
return <Grid item xs={12}>
|
||||
<Stack spacing={1} style={style}>
|
||||
<InputLabel htmlFor={name}>{label}</InputLabel>
|
||||
{label && <InputLabel htmlFor={name}>{label}</InputLabel>}
|
||||
<OutlinedInput
|
||||
id={name}
|
||||
type={type ? type : 'text'}
|
||||
value={formik.values[name]}
|
||||
value={value || (formik && formik.values[name])}
|
||||
name={name}
|
||||
multiline={multiline}
|
||||
onBlur={formik.handleBlur}
|
||||
onBlur={(...ar) => {
|
||||
return formik && formik.handleBlur(...ar);
|
||||
}}
|
||||
onChange={(...ar) => {
|
||||
onChange && onChange(...ar);
|
||||
return formik.handleChange(...ar);
|
||||
return formik && formik.handleChange(...ar);
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
fullWidth
|
||||
error={Boolean(formik.touched[name] && formik.errors[name])}
|
||||
error={Boolean(formik && formik.touched[name] && formik.errors[name])}
|
||||
/>
|
||||
{formik.touched[name] && formik.errors[name] && (
|
||||
{formik && formik.touched[name] && formik.errors[name] && (
|
||||
<FormHelperText error id="standard-weight-helper-text-name-login">
|
||||
{formik.errors[name]}
|
||||
</FormHelperText>
|
||||
)}
|
||||
{errors && (
|
||||
<FormHelperText error id="standard-weight-helper-text-name-login">
|
||||
{formik.errors[name]}
|
||||
</FormHelperText>
|
||||
|
@ -206,7 +213,7 @@ export const CosmosCollapse = ({ children, title }) => {
|
|||
export function CosmosFormDivider({title}) {
|
||||
return <Grid item xs={12}>
|
||||
<Divider>
|
||||
<Chip label={title} />
|
||||
{title && <Chip label={title} />}
|
||||
</Divider>
|
||||
</Grid>
|
||||
}
|
||||
|
|
302
client/src/pages/constellation/addDevice.jsx
Normal file
302
client/src/pages/constellation/addDevice.jsx
Normal file
|
@ -0,0 +1,302 @@
|
|||
// material-ui
|
||||
import { Alert, Button, InputLabel, OutlinedInput, 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 { CosmosCheckbox, CosmosFormDivider, CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
|
||||
import { DownloadFile } from '../../api/downloadButton';
|
||||
import QRCode from 'qrcode';
|
||||
import { useClientInfos } from '../../utils/hooks';
|
||||
|
||||
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, refreshConfig, devices }) => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [isDone, setIsDone] = useState(null);
|
||||
const canvasRef = React.useRef(null);
|
||||
const {role, nickname} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
let firstIP = "192.168.201.2/24";
|
||||
if (devices && devices.length > 0) {
|
||||
const isIpFree = (ip) => {
|
||||
return devices.filter((d) => d.ip === ip).length === 0;
|
||||
}
|
||||
let i = 1;
|
||||
let j = 201;
|
||||
while (!isIpFree(firstIP)) {
|
||||
i++;
|
||||
if (i > 254) {
|
||||
i = 0;
|
||||
j++;
|
||||
}
|
||||
firstIP = "192.168." + j + "." + i + "/24";
|
||||
}
|
||||
}
|
||||
|
||||
const renderCanvas = (data) => {
|
||||
if (!canvasRef.current) return setTimeout(() => {
|
||||
renderCanvas(data);
|
||||
}, 500);
|
||||
|
||||
QRCode.toCanvas(canvasRef.current, JSON.stringify(data),
|
||||
{
|
||||
width: 600,
|
||||
color: {
|
||||
dark: "#000",
|
||||
light: '#fff'
|
||||
}
|
||||
}, function (error) {
|
||||
if (error) console.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
return <>
|
||||
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<Formik
|
||||
initialValues={{
|
||||
nickname: nickname,
|
||||
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();
|
||||
renderCanvas(data.Config);
|
||||
}).catch((err) => {
|
||||
setErrors(err.response.data);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{(formik) => (
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<DialogTitle>Add Device</DialogTitle>
|
||||
|
||||
{isDone ? <DialogContent>
|
||||
<DialogContentText>
|
||||
<p>
|
||||
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"}>
|
||||
{/* {isDone.isLighthouse ? <>
|
||||
<CosmosFormDivider title={"Docker"} />
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
value={getDocker(isDone, false)}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
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"} />
|
||||
<DownloadFile
|
||||
filename={`constellation.yml`}
|
||||
content={isDone.Config}
|
||||
label={"Download constellation.yml"}
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContentText>
|
||||
</DialogContent> : <DialogContent>
|
||||
<DialogContentText>
|
||||
<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 &&
|
||||
(isAdmin ? <CosmosSelect
|
||||
name="nickname"
|
||||
label="Owner"
|
||||
formik={formik}
|
||||
// disabled={!isAdmin}
|
||||
options={
|
||||
users.map((u) => {
|
||||
return [u.nickname, u.nickname]
|
||||
})
|
||||
}
|
||||
/> : <>
|
||||
<InputLabel>Owner</InputLabel>
|
||||
<OutlinedInput
|
||||
fullWidth
|
||||
multiline
|
||||
value={nickname}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
disabled
|
||||
/>
|
||||
</>)}
|
||||
|
||||
<CosmosInputText
|
||||
name="deviceName"
|
||||
label="Device Name"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosInputText
|
||||
name="ip"
|
||||
label="Constellation IP Address"
|
||||
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) => {
|
||||
return <div>{err}</div>
|
||||
})}</Alert>
|
||||
</Stack>}
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
</DialogContentText>
|
||||
</DialogContent>}
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenModal(false)}>Close</Button>
|
||||
{!isDone && <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 />}
|
||||
>
|
||||
Add Device
|
||||
</ResponsiveButton>
|
||||
</>;
|
||||
};
|
||||
|
||||
export default AddDeviceModal;
|
174
client/src/pages/constellation/dns.jsx
Normal file
174
client/src/pages/constellation/dns.jsx
Normal file
|
@ -0,0 +1,174 @@
|
|||
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, CloudServerOutlined, CompassOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, TabletOutlined } from "@ant-design/icons";
|
||||
import IsLoggedIn from "../../isLoggedIn";
|
||||
import { Alert, Button, CircularProgress, InputLabel, 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";
|
||||
|
||||
export const ConstellationDNS = () => {
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [config, setConfig] = useState(null);
|
||||
|
||||
const refreshConfig = async () => {
|
||||
let configAsync = await API.config.get();
|
||||
setConfig(configAsync.data);
|
||||
setIsAdmin(configAsync.isAdmin);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refreshConfig();
|
||||
}, []);
|
||||
|
||||
return <>
|
||||
{(config) ? <>
|
||||
<Stack spacing={2} style={{maxWidth: "1000px"}}>
|
||||
<div>
|
||||
<MainCard title={"Constellation Internal DNS"} content={config.constellationIP}>
|
||||
<Stack spacing={2}>
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
Fallback: config.ConstellationConfig.DNSFallback,
|
||||
DNSBlockBlacklist: config.ConstellationConfig.DNSBlockBlacklist,
|
||||
DNSAdditionalBlocklists: config.ConstellationConfig.DNSAdditionalBlocklists || [],
|
||||
CustomDNSEntries: config.ConstellationConfig.CustomDNSEntries || []
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
let newConfig = { ...config };
|
||||
newConfig.ConstellationConfig.DNSFallback = values.Fallback;
|
||||
newConfig.ConstellationConfig.DNSBlockBlacklist = values.DNSBlockBlacklist;
|
||||
newConfig.ConstellationConfig.DNSAdditionalBlocklists = values.DNSAdditionalBlocklists;
|
||||
newConfig.ConstellationConfig.CustomDNSEntries = values.CustomDNSEntries;
|
||||
|
||||
return API.config.set(newConfig);
|
||||
}}
|
||||
>
|
||||
{(formik) => (
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<Stack spacing={2}>
|
||||
<Alert severity="info">This is a DNS that runs inside your Constellation network. It automatically
|
||||
rewrites your domains DNS entries to be local to your network, and also allows you to do things like block ads
|
||||
and trackers on all devices connected to your network. You can also add custom DNS entries to resolve to specific
|
||||
IP addresses. This DNS server is only accessible from inside your network.</Alert>
|
||||
|
||||
<CosmosInputText formik={formik} name="Fallback" label="DNS Fallback" placeholder={'8.8.8.8:53'} />
|
||||
|
||||
<CosmosFormDivider title={"DNS Blocklists"} />
|
||||
|
||||
<CosmosCheckbox formik={formik} name="DNSBlockBlacklist" label="Use Blacklists to block domains" />
|
||||
|
||||
<Alert severity="warning">When changing your DNS records, always use private mode on your browser and allow some times for various caches to expire.</Alert>
|
||||
|
||||
<InputLabel>DNS Blocklist URLs</InputLabel>
|
||||
{formik.values.DNSAdditionalBlocklists && formik.values.DNSAdditionalBlocklists.map((item, index) => (
|
||||
<Stack direction={"row"} spacing={2} key={`DNSAdditionalBlocklists${item}`} width={"100%"}>
|
||||
<DeleteButton onDelete={() => {
|
||||
formik.setFieldValue("DNSAdditionalBlocklists", [...formik.values.DNSAdditionalBlocklists.slice(0, index), ...formik.values.DNSAdditionalBlocklists.slice(index + 1)]);
|
||||
}} />
|
||||
<div style={{flexGrow: 1}}>
|
||||
<CosmosInputText
|
||||
value={item}
|
||||
name={`DNSAdditionalBlocklists${index}`}
|
||||
placeholder={'https://example.com/blocklist.txt'}
|
||||
onChange={(e) => {
|
||||
formik.setFieldValue("DNSAdditionalBlocklists", [...formik.values.DNSAdditionalBlocklists.slice(0, index), e.target.value, ...formik.values.DNSAdditionalBlocklists.slice(index + 1)]);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
))}
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Button variant="outlined" onClick={() => {
|
||||
formik.setFieldValue("DNSAdditionalBlocklists", [...formik.values.DNSAdditionalBlocklists, ""]);
|
||||
}}>Add</Button>
|
||||
<Button variant="outlined" onClick={() => {
|
||||
formik.setFieldValue("DNSAdditionalBlocklists", [
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||
"https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-only/hosts"
|
||||
]);
|
||||
}}>Reset Default</Button>
|
||||
</Stack>
|
||||
|
||||
<CosmosFormDivider title={"DNS Custom Entries"} />
|
||||
|
||||
<InputLabel>DNS Custom Entries</InputLabel>
|
||||
{formik.values.CustomDNSEntries && formik.values.CustomDNSEntries.map((item, index) => (
|
||||
<Stack direction={"row"} spacing={2} key={`CustomDNSEntries${item}`} width={"100%"}>
|
||||
<DeleteButton onDelete={() => {
|
||||
formik.setFieldValue("CustomDNSEntries", [...formik.values.CustomDNSEntries.slice(0, index), ...formik.values.CustomDNSEntries.slice(index + 1)]);
|
||||
}} />
|
||||
<div style={{flexGrow: 1}}>
|
||||
<CosmosInputText
|
||||
value={item.Key}
|
||||
name={`CustomDNSEntries${index}-key`}
|
||||
placeholder={'domain.com'}
|
||||
onChange={(e) => {
|
||||
const updatedCustomDNSEntries = [...formik.values.CustomDNSEntries];
|
||||
updatedCustomDNSEntries[index].Key = e.target.value;
|
||||
formik.setFieldValue("CustomDNSEntries", updatedCustomDNSEntries);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{flexGrow: 1}}>
|
||||
<CosmosInputText
|
||||
value={item.Value}
|
||||
name={`CustomDNSEntries${index}-value`}
|
||||
placeholder={'1213.123.123.123'}
|
||||
onChange={(e) => {
|
||||
const updatedCustomDNSEntries = [...formik.values.CustomDNSEntries];
|
||||
updatedCustomDNSEntries[index].Value = e.target.value;
|
||||
formik.setFieldValue("CustomDNSEntries", updatedCustomDNSEntries);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
))}
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Button variant="outlined" onClick={() => {
|
||||
formik.setFieldValue("CustomDNSEntries", [...formik.values.CustomDNSEntries, {
|
||||
Key: "",
|
||||
Value: "",
|
||||
Type: "A"
|
||||
}]);
|
||||
}}>Add</Button>
|
||||
<Button variant="outlined" onClick={() => {
|
||||
formik.setFieldValue("CustomDNSEntries", [
|
||||
]);
|
||||
}}>Reset</Button>
|
||||
</Stack>
|
||||
|
||||
<LoadingButton
|
||||
disableElevation
|
||||
loading={formik.isSubmitting}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Save
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</Stack>
|
||||
</MainCard>
|
||||
</div>
|
||||
</Stack>
|
||||
</> : <center>
|
||||
<CircularProgress color="inherit" size={20} />
|
||||
</center>}
|
||||
</>
|
||||
};
|
57
client/src/pages/constellation/index.jsx
Normal file
57
client/src/pages/constellation/index.jsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import * as React from 'react';
|
||||
import MainCard from '../../components/MainCard';
|
||||
import { Alert, Chip, Divider, Stack, useMediaQuery } from '@mui/material';
|
||||
import HostChip from '../../components/hostChip';
|
||||
import { RouteMode, RouteSecurity } from '../../components/routeComponents';
|
||||
import { getFaviconURL } from '../../utils/routes';
|
||||
import * as API from '../../api';
|
||||
import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
|
||||
import IsLoggedIn from '../../isLoggedIn';
|
||||
import PrettyTabbedView from '../../components/tabbedView/tabbedView';
|
||||
import { useClientInfos } from '../../utils/hooks';
|
||||
|
||||
import { ConstellationVPN } from './vpn';
|
||||
import { ConstellationDNS } from './dns';
|
||||
|
||||
const ConstellationIndex = () => {
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
return isAdmin ? <div>
|
||||
<IsLoggedIn />
|
||||
|
||||
<PrettyTabbedView path="/cosmos-ui/constellation/:tab" tabs={[
|
||||
{
|
||||
title: 'VPN',
|
||||
children: <ConstellationVPN />,
|
||||
path: 'vpn'
|
||||
},
|
||||
{
|
||||
title: 'DNS',
|
||||
children: <ConstellationDNS />,
|
||||
path: 'dns'
|
||||
},
|
||||
{
|
||||
title: 'Firewall',
|
||||
children: <div>
|
||||
<Alert severity="info">
|
||||
Coming soon. This feature will allow you to open and close ports individually
|
||||
on each device and decide who can access them.
|
||||
</Alert>
|
||||
</div>,
|
||||
},
|
||||
{
|
||||
title: 'Unsafe Routes',
|
||||
children: <div>
|
||||
<Alert severity="info">
|
||||
Coming soon. This feature will allow you to tunnel your traffic through
|
||||
your devices to things outside of your constellation.
|
||||
</Alert>
|
||||
</div>,
|
||||
}
|
||||
]}/>
|
||||
|
||||
</div> : <ConstellationVPN />;
|
||||
}
|
||||
|
||||
export default ConstellationIndex;
|
219
client/src/pages/constellation/vpn.jsx
Normal file
219
client/src/pages/constellation/vpn.jsx
Normal file
|
@ -0,0 +1,219 @@
|
|||
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, CloudServerOutlined, CompassOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, TabletOutlined } from "@ant-design/icons";
|
||||
import IsLoggedIn from "../../isLoggedIn";
|
||||
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";
|
||||
import { useClientInfos } from "../../utils/hooks";
|
||||
|
||||
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 ConstellationVPN = () => {
|
||||
const [config, setConfig] = useState(null);
|
||||
const [users, setUsers] = useState(null);
|
||||
const [devices, setDevices] = useState(null);
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
const refreshConfig = async () => {
|
||||
let configAsync = await API.config.get();
|
||||
setConfig(configAsync.data);
|
||||
setDevices((await API.constellation.list()).data || []);
|
||||
if(isAdmin)
|
||||
setUsers((await API.users.list()).data || []);
|
||||
else
|
||||
setUsers([]);
|
||||
};
|
||||
|
||||
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 if (r.deviceName.toLowerCase().includes("lighthouse") || r.deviceName.toLowerCase().includes("server")) {
|
||||
return <CompassOutlined />
|
||||
} else {
|
||||
return <CloudOutlined />
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
{(devices && config && users) ? <>
|
||||
<Stack spacing={2} style={{maxWidth: "1000px"}}>
|
||||
<div>
|
||||
<Alert severity="info">
|
||||
Constellation is a VPN that runs inside your Cosmos network. It automatically
|
||||
connects all your devices together, and allows you to access them from anywhere.
|
||||
Please refer to the <a href="https://cosmos-cloud.io/doc/61 Constellation VPN/" target="_blank">documentation</a> for more information.
|
||||
In order to connect, please use the <a href="https://cosmos-cloud.io/clients" target="_blank">Constellation App</a>.
|
||||
</Alert>
|
||||
<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,
|
||||
PrivateNode: config.ConstellationConfig.PrivateNode,
|
||||
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.PrivateNode = values.PrivateNode;
|
||||
newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay;
|
||||
newConfig.ConstellationConfig.ConstellationHostname = values.ConstellationHostname;
|
||||
setTimeout(() => {
|
||||
refreshConfig();
|
||||
}, 1500);
|
||||
return API.config.set(newConfig);
|
||||
}}
|
||||
>
|
||||
{(formik) => (
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<Stack spacing={2}>
|
||||
{formik.values.Enabled && <Stack spacing={2} direction="row">
|
||||
<Button
|
||||
disableElevation
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={async () => {
|
||||
await API.constellation.restart();
|
||||
}}
|
||||
>
|
||||
Restart VPN Service
|
||||
</Button>
|
||||
<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" />
|
||||
{config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <>
|
||||
{formik.values.Enabled && <>
|
||||
<CosmosCheckbox formik={formik} name="IsRelay" label="Relay requests via this Node" />
|
||||
<CosmosCheckbox formik={formik} name="PrivateNode" label="This node is Private (no public IP)" />
|
||||
{!formik.values.PrivateNode && <>
|
||||
<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" />
|
||||
</>}
|
||||
</>}
|
||||
</>}
|
||||
<LoadingButton
|
||||
disableElevation
|
||||
loading={formik.isSubmitting}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Save
|
||||
</LoadingButton>
|
||||
<UploadButtons
|
||||
accept=".yml,.yaml"
|
||||
label={"Upload External Constellation Network File"}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
OnChange={async (e) => {
|
||||
let file = e.target.files[0];
|
||||
await API.constellation.connect(file);
|
||||
setTimeout(() => {
|
||||
refreshConfig();
|
||||
}, 1000);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</Stack>
|
||||
</MainCard>
|
||||
</div>
|
||||
{config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <>
|
||||
<CosmosFormDivider title={"Devices"} />
|
||||
<PrettyTableView
|
||||
data={devices.filter((d) => !d.blocked)}
|
||||
getKey={(r) => r.deviceName}
|
||||
buttons={[
|
||||
<AddDeviceModal users={users} 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: 'Type',
|
||||
field: (r) => <strong>{r.isLighthouse ? "Lighthouse" : "Client"}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Constellation IP',
|
||||
screenMin: 'md',
|
||||
field: (r) => r.ip,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
clickable: true,
|
||||
field: (r) => {
|
||||
return <DeleteButton onDelete={async () => {
|
||||
await API.constellation.block(r.nickname, r.deviceName, true);
|
||||
refreshConfig();
|
||||
}}></DeleteButton>
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</>}
|
||||
</Stack>
|
||||
</> : <center>
|
||||
<CircularProgress color="inherit" size={20} />
|
||||
</center>}
|
||||
</>
|
||||
};
|
|
@ -12,6 +12,7 @@ import { getFullOrigin } from "../../utils/routes";
|
|||
import IsLoggedIn from "../../isLoggedIn";
|
||||
import { ServAppIcon } from "../../utils/servapp-icon";
|
||||
import Chart from 'react-apexcharts';
|
||||
import { useClientInfos } from "../../utils/hooks";
|
||||
|
||||
|
||||
export const HomeBackground = () => {
|
||||
|
@ -87,6 +88,8 @@ const HomePage = () => {
|
|||
const theme = useTheme();
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
const isMd = useMediaQuery(theme.breakpoints.up('md'));
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
const blockStyle = {
|
||||
margin: 0,
|
||||
|
@ -112,9 +115,13 @@ const HomePage = () => {
|
|||
}
|
||||
|
||||
const refreshConfig = () => {
|
||||
API.docker.list().then((res) => {
|
||||
setServApps(res.data);
|
||||
});
|
||||
if(isAdmin) {
|
||||
API.docker.list().then((res) => {
|
||||
setServApps(res.data);
|
||||
});
|
||||
} else {
|
||||
setServApps([]);
|
||||
}
|
||||
API.config.get().then((res) => {
|
||||
setConfig(res.data);
|
||||
});
|
||||
|
@ -213,47 +220,47 @@ const HomePage = () => {
|
|||
<HomeBackground status={coStatus} />
|
||||
<TransparentHeader />
|
||||
<Stack style={{ zIndex: 2 }} spacing={1}>
|
||||
{coStatus && !coStatus.database && (
|
||||
{isAdmin && coStatus && !coStatus.database && (
|
||||
<Alert severity="error">
|
||||
No Database is setup for Cosmos! User Management and Authentication will not work.<br />
|
||||
You can either setup the database, or disable user management in the configuration panel.<br />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.letsencrypt && (
|
||||
{isAdmin && coStatus && coStatus.letsencrypt && (
|
||||
<Alert severity="error">
|
||||
You have enabled Let's Encrypt for automatic HTTPS Certificate. You need to provide the configuration with an email address to use for Let's Encrypt in the configs.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.LetsEncryptErrors && coStatus.LetsEncryptErrors.length > 0 && (
|
||||
{isAdmin && coStatus && coStatus.LetsEncryptErrors && coStatus.LetsEncryptErrors.length > 0 && (
|
||||
<Alert severity="error">
|
||||
There are errors with your Let's Encrypt configuration or one of your routes, please fix them as soon as possible.:
|
||||
There are errors with your Let's Encrypt configuration or one of your routes, please fix them as soon as possible:
|
||||
{coStatus.LetsEncryptErrors.map((err) => {
|
||||
return <div> - {err}</div>
|
||||
})}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.newVersionAvailable && (
|
||||
{isAdmin && coStatus && coStatus.newVersionAvailable && (
|
||||
<Alert severity="warning">
|
||||
A new version of Cosmos is available! Please update to the latest version to get the latest features and bug fixes.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.needsRestart && (
|
||||
{isAdmin && coStatus && coStatus.needsRestart && (
|
||||
<Alert severity="warning">
|
||||
You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.domain && (
|
||||
{isAdmin && coStatus && coStatus.domain && (
|
||||
<Alert severity="error">
|
||||
You are using localhost or 0.0.0.0 as a hostname in the configuration. It is recommended that you use a domain name or an IP instead.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && !coStatus.docker && (
|
||||
{isAdmin && coStatus && !coStatus.docker && (
|
||||
<Alert severity="error">
|
||||
Docker is not connected! Please check your docker connection.<br />
|
||||
Did you forget to add <pre>-v /var/run/docker.sock:/var/run/docker.sock</pre> to your docker run command?<br />
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Link as LinkMUI } from '@mui/material'
|
|||
import DockerComposeImport from '../servapps/containers/docker-compose';
|
||||
import { AppstoreAddOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import ResponsiveButton from "../../components/responseiveButton";
|
||||
import { useClientInfos } from "../../utils/hooks";
|
||||
|
||||
function Screenshots({ screenshots }) {
|
||||
return screenshots.length > 1 ? (
|
||||
|
@ -23,17 +24,17 @@ function Screenshots({ screenshots }) {
|
|||
: <img src={screenshots[0]} style={{ maxHeight: '300px', height: '100%', maxWidth: '100%' }} />
|
||||
}
|
||||
|
||||
function Showcases({ showcase, isDark }) {
|
||||
function Showcases({ showcase, isDark, isAdmin }) {
|
||||
return (
|
||||
<Carousel animation="slide" navButtonsAlwaysVisible={false} fullHeightHover="true" swipe={false}>
|
||||
{
|
||||
showcase.map((item, i) => <ShowcasesItem isDark={isDark} key={i} item={item} />)
|
||||
showcase.map((item, i) => <ShowcasesItem isDark={isDark} key={i} item={item} isAdmin={isAdmin} />)
|
||||
}
|
||||
</Carousel>
|
||||
)
|
||||
}
|
||||
|
||||
function ShowcasesItem({ isDark, item }) {
|
||||
function ShowcasesItem({ isDark, item, isAdmin }) {
|
||||
return (
|
||||
<Paper style={{
|
||||
position: 'relative',
|
||||
|
@ -68,9 +69,9 @@ function ShowcasesItem({ isDark, item }) {
|
|||
overflow: 'hidden',
|
||||
}}></p>
|
||||
<Stack direction="row" spacing={2} justifyContent="flex-start">
|
||||
<div>
|
||||
{isAdmin && <div>
|
||||
<DockerComposeImport installerInit defaultName={item.name} dockerComposeInit={item.compose} />
|
||||
</div>
|
||||
</div>}
|
||||
<Link to={"/cosmos-ui/market-listing/cosmos-cloud/" + item.name} style={{
|
||||
textDecoration: 'none',
|
||||
}}>
|
||||
|
@ -110,6 +111,8 @@ const MarketPage = () => {
|
|||
const isDark = theme.palette.mode === 'dark';
|
||||
const { appName, appStore } = useParams();
|
||||
const [search, setSearch] = useState("");
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
const backgroundStyle = isDark ? {
|
||||
backgroundColor: 'rgb(0,0,0)',
|
||||
|
@ -178,7 +181,7 @@ const MarketPage = () => {
|
|||
</Link>
|
||||
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Screenshots screenshots={openedApp.screenshots} />
|
||||
<Screenshots screenshots={openedApp.screenshots} isAdmin={isAdmin}/>
|
||||
</div>
|
||||
|
||||
<Stack direction="row" spacing={2}>
|
||||
|
@ -202,9 +205,9 @@ const MarketPage = () => {
|
|||
|
||||
<div dangerouslySetInnerHTML={{ __html: openedApp.longDescription }}></div>
|
||||
|
||||
<div>
|
||||
{isAdmin && <div>
|
||||
<DockerComposeImport installerInit defaultName={openedApp.name} dockerComposeInit={openedApp.compose} />
|
||||
</div>
|
||||
</div>}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>}
|
||||
|
@ -223,7 +226,7 @@ const MarketPage = () => {
|
|||
size={100}
|
||||
/>
|
||||
</Box>}
|
||||
{showcase && showcase.length > 0 && <Showcases showcase={showcase} isDark={isDark} />}
|
||||
{showcase && showcase.length > 0 && <Showcases showcase={showcase} isDark={isDark} isAdmin={isAdmin} />}
|
||||
</Stack>
|
||||
|
||||
<Stack spacing={1} style={{
|
||||
|
|
|
@ -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 />
|
||||
|
|
29
client/src/utils/hooks.js
Normal file
29
client/src/utils/hooks.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
import { useCookies } from 'react-cookie';
|
||||
import { logout } from '../api/authentication';
|
||||
|
||||
function useClientInfos() {
|
||||
const [cookies] = useCookies(['client-infos']);
|
||||
|
||||
let clientInfos = null;
|
||||
|
||||
try {
|
||||
// Try to parse the cookie into a JavaScript object
|
||||
clientInfos = cookies['client-infos'].split(',');
|
||||
|
||||
return {
|
||||
nickname: clientInfos[0],
|
||||
role: clientInfos[1]
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error parsing client-infos cookie:', error);
|
||||
return {
|
||||
nickname: "",
|
||||
role: 2
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
useClientInfos
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
27
go.mod
27
go.mod
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/docker/docker v23.0.3+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/foomo/tlsconfig v0.0.0-20180418120404-b67861b076c9
|
||||
github.com/go-acme/lego/v4 v4.13.3
|
||||
github.com/go-acme/lego/v4 v4.14.2
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-chi/httprate v0.7.1
|
||||
github.com/go-playground/validator/v10 v10.14.0
|
||||
|
@ -16,7 +16,9 @@ require (
|
|||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jasonlvhit/gocron v0.0.1
|
||||
github.com/miekg/dns v1.1.55
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||
github.com/ory/fosite v0.44.0
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/pquerna/otp v1.4.0
|
||||
|
@ -27,6 +29,7 @@ require (
|
|||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/net v0.11.0
|
||||
golang.org/x/sys v0.9.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -57,7 +60,20 @@ require (
|
|||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/andybalholm/cascadia v1.1.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
|
||||
github.com/aws/aws-sdk-go v1.39.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.19.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
|
@ -126,7 +142,6 @@ require (
|
|||
github.com/magiconair/properties v1.8.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/goveralls v0.0.6 // indirect
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
|
@ -137,6 +152,7 @@ require (
|
|||
github.com/montanaflynn/stats v0.7.0 // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
||||
github.com/nrdcg/desec v0.7.0 // indirect
|
||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||
|
@ -153,7 +169,7 @@ require (
|
|||
github.com/ory/viper v1.7.5 // indirect
|
||||
github.com/ory/x v0.0.214 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
github.com/ovh/go-ovh v1.4.1 // indirect
|
||||
github.com/ovh/go-ovh v1.4.2 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pborman/uuid v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||
|
@ -167,7 +183,6 @@ require (
|
|||
github.com/sacloud/packages-go v0.0.9 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/softlayer/softlayer-go v1.1.2 // indirect
|
||||
|
@ -210,9 +225,9 @@ require (
|
|||
google.golang.org/grpc v1.53.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.6 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
)
|
||||
|
|
48
go.sum
48
go.sum
|
@ -60,6 +60,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
|
|||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DataDog/datadog-go v4.0.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
|
@ -107,9 +108,35 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0
|
|||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/aws/aws-sdk-go v1.23.19/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
|
||||
github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo=
|
||||
github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go-v2 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k=
|
||||
github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 h1:hMUCiE3Zi5AHrRNGf5j985u0WyqI6r2NULhUfo0N/No=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 h1:yOpYx+FTBdpk/g+sBU6Cb1H0U/TLEcYYp66mYqsPpcc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 h1:IiDolu/eLmuB18DRZibj77n1hHQT7z12jnGO7Ze3pLc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 h1:PwNeYoonBzmTdCztKiiutws3U24KrnDBuabzRfIlZY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2/go.mod h1:gQhLZrTEath4zik5ixIe6axvgY5jJrgSBDJ360Fxnco=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 h1:p4mTxJfCAyiTT4Wp6p/mOPa6j5MqCSRGot8qZwFs+Z0=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4/go.mod h1:VBLWpaHvhQNeu7N9rMEf00SWeOONb/HvaDUxe/7b44k=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY=
|
||||
github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDyVNp2tHXRq04=
|
||||
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
|
@ -259,8 +286,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
|
|||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-acme/lego/v4 v4.13.3 h1:aZ1S9FXIkCWG3Uw/rZKSD+MOuO8ZB1t6p9VCg6jJiNY=
|
||||
github.com/go-acme/lego/v4 v4.13.3/go.mod h1:c/iodVGMeBXG/+KiQczoNkySo3YLWTVa0kiyeVd/FHc=
|
||||
github.com/go-acme/lego/v4 v4.14.2 h1:/D/jqRgLi8Cbk33sLGtu2pX2jEg3bGJWHyV8kFuUHGM=
|
||||
github.com/go-acme/lego/v4 v4.14.2/go.mod h1:kBXxbeTg0x9AgaOYjPSwIeJy3Y33zTz+tMD16O4MO6c=
|
||||
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
|
@ -672,6 +699,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
|
||||
|
@ -1024,11 +1052,15 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J
|
|||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
|
||||
github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 h1:qpB3wZR4+MPK92cTC9zZPnndkJgDgPvQqPUAgVc1NXU=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9/go.mod h1:HUoHXDrFvidN1NK9Wb/mZKNOfDNutKkzF2Pg71M9hHA=
|
||||
github.com/nrdcg/desec v0.7.0 h1:iuGhi4pstF3+vJWwt292Oqe2+AsSPKDynQna/eu1fDs=
|
||||
github.com/nrdcg/desec v0.7.0/go.mod h1:e1uRqqKv1mJdd5+SQROAhmy75lKMphLzWIuASLkpeFY=
|
||||
github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
|
||||
|
@ -1124,8 +1156,8 @@ github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6
|
|||
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
||||
github.com/ovh/go-ovh v1.4.1 h1:VBGa5wMyQtTP7Zb+w97zRCh9sLtM/2YKRyy+MEJmWaM=
|
||||
github.com/ovh/go-ovh v1.4.1/go.mod h1:6bL6pPyUT7tBfI0pqOegJgRjgjuO+mOo+MyXd1EEC0M=
|
||||
github.com/ovh/go-ovh v1.4.2 h1:ub4jVK6ERbiBTo4y5wbLCjeKCjGY+K36e7BviW+MaAU=
|
||||
github.com/ovh/go-ovh v1.4.2/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=
|
||||
github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
|
@ -1241,8 +1273,6 @@ github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UD
|
|||
github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 h1:ZTzdx88+AcnjqUfJwnz89UBrMSBQ1NEysg9u5d+dU9c=
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04/go.mod h1:5KS21fpch8TIMyAUv/qQqTa3GZfBDYgjaZbd2KXKYfg=
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
|
||||
|
@ -1877,6 +1907,8 @@ gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.6 h1:mCPl7q0jbIGACXvGBljAuuApmKZo3rRi4tlRIEbMvjA=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.6/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
|
|
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.8.3",
|
||||
"version": "0.10.0-unstable16",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cosmos-server",
|
||||
"version": "0.8.3",
|
||||
"version": "0.10.0-unstable16",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
|
@ -36,6 +36,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.4.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^6.1.1",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
|
@ -3708,6 +3709,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
|
||||
"integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q=="
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz",
|
||||
"integrity": "sha512-DBpRoJGKJZn7RY92dPrgoMew8xCWc2P71beqsjyhEI/Ds9mOyVmBwtekyfhpwFIVt1WrxTonFifiOZ62V8CnNA=="
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
|
||||
|
@ -8900,6 +8906,19 @@
|
|||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-cookie": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-6.1.1.tgz",
|
||||
"integrity": "sha512-fuFRpf8LH6SfmVMowDUIRywJF5jAUDUWrm0EI5VdXfTl5bPcJ7B0zWbuYpT0Tvikx7Gs18MlvAT+P+744dUz2g==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"universal-cookie": "^6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-copy-to-clipboard": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
|
||||
|
@ -10368,6 +10387,15 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-cookie": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-6.1.1.tgz",
|
||||
"integrity": "sha512-33S9x3CpdUnnjwTNs2Fgc41WGve2tdLtvaK2kPSbZRc5pGpz2vQFbRWMxlATsxNNe/Cy8SzmnmbuBM85jpZPtA==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.5.1",
|
||||
"cookie": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.9.21",
|
||||
"version": "0.10.0-unstable31",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
@ -36,6 +36,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.4.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^6.1.1",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
|
@ -63,13 +64,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 53:53 -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"
|
||||
},
|
||||
|
|
10
readme.md
10
readme.md
|
@ -7,8 +7,7 @@
|
|||
<p align="center"><a href="https://github.com/DrMxrcy"><img src="https://avatars.githubusercontent.com/DrMxrcy" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
<a href="https://github.com/soldier1"><img src="https://avatars.githubusercontent.com/soldier1" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
<a href="https://github.com/devcircus"><img src="https://avatars.githubusercontent.com/devcircus" style="border-radius:48px" width="48" height="48" alt="Clayton Stone" title="Clayton Stone" /></a>
|
||||
<a href="https://github.com/BillyDas"><img src="https://avatars.githubusercontent.com/BillyDas" style="border-radius:48px" width="48" height="48" alt="Billy Das" title="Billy Das" /></a>
|
||||
<a href="https://github.com/Serph91P"><img src="https://avatars.githubusercontent.com/Serph91P" style="border-radius:48px" width="48" height="48" alt="Seraph91P" title="Seraph91P" /></a>
|
||||
<a href="https://github.com/BlackrazorNZ"><img src="https://avatars.githubusercontent.com/BlackrazorNZ" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
</p><!-- /sponsors -->
|
||||
|
||||
---
|
||||
|
@ -45,6 +44,7 @@ Cosmos is a:
|
|||
* **Reverse-Proxy** 🔄🔗 Targeting containers, other servers, or serving static folders / SPA with **automatic HTTPS**, and a **nice UI**
|
||||
* **Authentication Server** 👦👩 With strong security, **multi-factor authentication** and multiple strategies (**OpenId**, forward headers, HTML)
|
||||
* **Container manager** 🐋🔧 To easily manage your containers and their settings, keep them up to date as well as audit their security. Includes docker-compose support!
|
||||
* **VPN** 🌐🔒 To securely access your applications from anywhere, without having to open ports on your router.
|
||||
* **Identity Provider** 👦👩 To easily manage your users, **invite your friends and family** to your applications without awkardly sharing credentials. Let them request a password change with an email rather than having you unlock their account manually!
|
||||
* **SmartShield technology** 🧠🛡 Automatically secure your applications without manual adjustments (see below for more details). Includes anti-bot and anti-DDOS strategies.
|
||||
|
||||
|
@ -146,15 +146,17 @@ Note that **you are allowed** to use it to host a monetized business website, a
|
|||
Installation is simple using Docker:
|
||||
|
||||
```
|
||||
docker run -d -p 80:80 -p 443:443 --privileged --name cosmos-server -h cosmos-server --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /:/mnt/host -v /var/lib/cosmos:/config azukaar/cosmos-server:latest
|
||||
docker run -d -p 80:80 -p 443:443 -p 4242:4242/udp --privileged --name cosmos-server -h cosmos-server --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /:/mnt/host -v /var/lib/cosmos:/config azukaar/cosmos-server:latest
|
||||
```
|
||||
|
||||
in this command, `-v /:/mnt/host` is optional and allow to manage folders from Cosmos, you can remove it if you don't want it but you will have to create your container's bind folders manually.
|
||||
|
||||
`--privileged` is also optional, but it is required if you use hardening software like AppArmor or SELinux, as they restrict access to the docker socket.
|
||||
`--privileged` is also optional, but it is required if you use hardening software like AppArmor or SELinux, as they restrict access to the docker socket. It is also required for Constellation to work. If you don't want to use it, you can add the following capabilities: NET_ADMIN for Constellation.
|
||||
|
||||
Once installed, simply go to `http://your-server-ip` and follow the instructions of the setup wizard.
|
||||
|
||||
Port 4242 is a UDP port used for the Constellation VPN.
|
||||
|
||||
make sure you expose the right ports (by default 80 / 443). It is best to keep those ports intacts, as Cosmos is meant to run as your reverse proxy. Trying to setup Cosmos behind another reverse proxy is possible but will only create headaches.
|
||||
|
||||
You also need to keep the docker socket mounted, as Cosmos needs to be able to manage your containers.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/azukaar/cosmos-server/src/authorizationserver"
|
||||
"github.com/azukaar/cosmos-server/src/constellation"
|
||||
)
|
||||
|
||||
func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
||||
|
@ -43,6 +44,7 @@ func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
|||
utils.DisconnectDB()
|
||||
authorizationserver.Init()
|
||||
utils.RestartHTTPServer()
|
||||
constellation.RestartNebula()
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
|
|
190
src/constellation/DNS.go
Normal file
190
src/constellation/DNS.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"time"
|
||||
"strconv"
|
||||
"strings"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
var DNSBlacklist = map[string]bool{}
|
||||
|
||||
func externalLookup(client *dns.Client, r *dns.Msg, serverAddr string) (*dns.Msg, time.Duration, error) {
|
||||
rCopy := r.Copy() // Create a copy of the request to forward
|
||||
rCopy.Id = dns.Id() // Assign a new ID for the forwarded request
|
||||
|
||||
// Enable DNSSEC
|
||||
rCopy.SetEdns0(4096, true)
|
||||
rCopy.CheckingDisabled = false
|
||||
rCopy.MsgHdr.AuthenticatedData = true
|
||||
|
||||
return client.Exchange(rCopy, serverAddr)
|
||||
}
|
||||
|
||||
func handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
config := utils.GetMainConfig()
|
||||
DNSFallback := config.ConstellationConfig.DNSFallback
|
||||
|
||||
if DNSFallback == "" {
|
||||
DNSFallback = "8.8.8.8:53"
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Authoritative = true
|
||||
|
||||
customHandled := false
|
||||
|
||||
// []string hostnames
|
||||
hostnames := utils.GetAllHostnames(false, true)
|
||||
|
||||
if !customHandled {
|
||||
customDNSEntries := config.ConstellationConfig.CustomDNSEntries
|
||||
|
||||
// Overwrite local hostnames with custom entries
|
||||
for _, q := range r.Question {
|
||||
for _, entry := range customDNSEntries {
|
||||
hostname := entry.Key
|
||||
ip := entry.Value
|
||||
|
||||
if strings.HasSuffix(q.Name, hostname + ".") && q.Qtype == dns.TypeA {
|
||||
utils.Debug("DNS Overwrite " + hostname + " with " + ip)
|
||||
rr, _ := dns.NewRR(q.Name + " A " + ip)
|
||||
m.Answer = append(m.Answer, rr)
|
||||
customHandled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !customHandled {
|
||||
// Overwrite local hostnames with Constellation IP
|
||||
for _, q := range r.Question {
|
||||
utils.Debug("DNS Question " + q.Name)
|
||||
for _, hostname := range hostnames {
|
||||
if strings.HasSuffix(q.Name, hostname + ".") && q.Qtype == dns.TypeA {
|
||||
utils.Debug("DNS Overwrite " + hostname + " with 192.168.201.1")
|
||||
rr, _ := dns.NewRR(q.Name + " A 192.168.201.1")
|
||||
m.Answer = append(m.Answer, rr)
|
||||
customHandled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !customHandled {
|
||||
// Block blacklisted domains
|
||||
for _, q := range r.Question {
|
||||
noDot := strings.TrimSuffix(q.Name, ".")
|
||||
if DNSBlacklist[noDot] {
|
||||
if q.Qtype == dns.TypeA {
|
||||
utils.Debug("DNS Block " + noDot)
|
||||
rr, _ := dns.NewRR(q.Name + " A 0.0.0.0")
|
||||
m.Answer = append(m.Answer, rr)
|
||||
}
|
||||
|
||||
customHandled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not custom handled, use external DNS
|
||||
if !customHandled {
|
||||
client := new(dns.Client)
|
||||
externalResponse, time, err := externalLookup(client, r, DNSFallback)
|
||||
if err != nil {
|
||||
utils.Error("Failed to forward query:", err)
|
||||
return
|
||||
}
|
||||
utils.Debug("DNS Forwarded DNS query to "+DNSFallback+" in " + time.String())
|
||||
|
||||
externalResponse.Id = r.Id
|
||||
|
||||
m = externalResponse
|
||||
}
|
||||
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func loadRawBlockList(DNSBlacklistRaw string) {
|
||||
DNSBlacklistArray := strings.Split(string(DNSBlacklistRaw), "\n")
|
||||
for _, domain := range DNSBlacklistArray {
|
||||
if domain != "" && !strings.HasPrefix(domain, "#") {
|
||||
splitDomain := strings.Split(domain, " ")
|
||||
if len(splitDomain) == 1 && isDomain(splitDomain[0]) {
|
||||
DNSBlacklist[splitDomain[0]] = true
|
||||
} else if len(splitDomain) == 2 {
|
||||
if isDomain(splitDomain[0]) {
|
||||
DNSBlacklist[splitDomain[0]] = true
|
||||
} else if isDomain(splitDomain[1]) {
|
||||
DNSBlacklist[splitDomain[1]] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func InitDNS() {
|
||||
config := utils.GetMainConfig()
|
||||
DNSPort := config.ConstellationConfig.DNSPort
|
||||
DNSBlockBlacklist := config.ConstellationConfig.DNSBlockBlacklist
|
||||
|
||||
if DNSPort == "" {
|
||||
DNSPort = "53"
|
||||
}
|
||||
|
||||
if DNSBlockBlacklist {
|
||||
DNSBlacklist = map[string]bool{}
|
||||
blacklistPath := utils.CONFIGFOLDER + "dns-blacklist.txt"
|
||||
|
||||
utils.Log("Loading DNS blacklist from " + blacklistPath)
|
||||
|
||||
fileExist := utils.FileExists(blacklistPath)
|
||||
if fileExist {
|
||||
DNSBlacklistRaw, err := ioutil.ReadFile(blacklistPath)
|
||||
if err != nil {
|
||||
utils.Error("Failed to load DNS blacklist", err)
|
||||
} else {
|
||||
loadRawBlockList(string(DNSBlacklistRaw))
|
||||
}
|
||||
} else {
|
||||
utils.Log("No DNS blacklist found")
|
||||
}
|
||||
|
||||
// download additional blocklists from config.DNSAdditionalBlocklists []string
|
||||
for _, url := range config.ConstellationConfig.DNSAdditionalBlocklists {
|
||||
utils.Log("Downloading DNS blacklist from " + url)
|
||||
DNSBlacklistRaw, err := utils.DownloadFile(url)
|
||||
if err != nil {
|
||||
utils.Error("Failed to download DNS blacklist", err)
|
||||
} else {
|
||||
loadRawBlockList(DNSBlacklistRaw)
|
||||
}
|
||||
}
|
||||
|
||||
utils.Log("Loaded " + strconv.Itoa(len(DNSBlacklist)) + " domains")
|
||||
}
|
||||
|
||||
if(!config.ConstellationConfig.DNSDisabled) {
|
||||
go (func() {
|
||||
dns.HandleFunc(".", handleDNSRequest)
|
||||
server := &dns.Server{Addr: ":" + DNSPort, Net: "udp"}
|
||||
|
||||
utils.Log("Starting DNS server on :" + DNSPort)
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
utils.Fatal("Failed to start server: %s\n", err)
|
||||
}
|
||||
})()
|
||||
}
|
||||
}
|
101
src/constellation/api_devices_block.go
Normal file
101
src/constellation/api_devices_block.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
type DeviceBlockRequestJSON struct {
|
||||
Nickname string `json:"nickname",validate:"required,min=3,max=32,alphanum"`
|
||||
DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"`
|
||||
Block bool `json:"block",omitempty`
|
||||
}
|
||||
|
||||
func DeviceBlock(w http.ResponseWriter, req *http.Request) {
|
||||
if(req.Method == "POST") {
|
||||
var request DeviceBlockRequestJSON
|
||||
err1 := json.NewDecoder(req.Body).Decode(&request)
|
||||
if err1 != nil {
|
||||
utils.Error("ConstellationDeviceBlocking: Invalid User Request", err1)
|
||||
utils.HTTPError(w, "Device Creation Error",
|
||||
http.StatusInternalServerError, "DB001")
|
||||
return
|
||||
}
|
||||
|
||||
errV := utils.Validate.Struct(request)
|
||||
if errV != nil {
|
||||
utils.Error("DeviceBlocking: Invalid User Request", errV)
|
||||
utils.HTTPError(w, "Device Creation Error: " + errV.Error(),
|
||||
http.StatusInternalServerError, "DB002")
|
||||
return
|
||||
}
|
||||
|
||||
nickname := utils.Sanitize(request.Nickname)
|
||||
deviceName := utils.Sanitize(request.DeviceName)
|
||||
|
||||
if utils.AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Log("ConstellationDeviceBlocking: Blocking Device " + deviceName)
|
||||
|
||||
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("ConstellationDeviceBlocking: Blocking Device " + deviceName)
|
||||
|
||||
err2 := c.FindOne(nil, map[string]interface{}{
|
||||
"DeviceName": deviceName,
|
||||
"Nickname": nickname,
|
||||
"Blocked": false,
|
||||
}).Decode(&device)
|
||||
|
||||
if err2 == nil {
|
||||
utils.Debug("ConstellationDeviceBlocking: Found Device " + deviceName)
|
||||
|
||||
_, err3 := c.UpdateOne(nil, map[string]interface{}{
|
||||
"DeviceName": deviceName,
|
||||
"Nickname": nickname,
|
||||
}, map[string]interface{}{
|
||||
"$set": map[string]interface{}{
|
||||
"Blocked": request.Block,
|
||||
},
|
||||
})
|
||||
|
||||
if err3 != nil {
|
||||
utils.Error("DeviceBlocking: Error while updating device", err3)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err3.Error(),
|
||||
http.StatusInternalServerError, "DB001")
|
||||
return
|
||||
}
|
||||
|
||||
if request.Block {
|
||||
utils.Log("ConstellationDeviceBlocking: Device " + deviceName + " blocked")
|
||||
} else {
|
||||
utils.Log("ConstellationDeviceBlocking: Device " + deviceName + " unblocked")
|
||||
}
|
||||
} else {
|
||||
utils.Error("DeviceBlocking: Error while finding device", err2)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err2.Error(),
|
||||
http.StatusInternalServerError, "DB001")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
})
|
||||
} else {
|
||||
utils.Error("DeviceBlocking: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
41
src/constellation/api_devices_config.go
Normal file
41
src/constellation/api_devices_config.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package constellation
|
||||
|
||||
// import (
|
||||
// "net/http"
|
||||
// "encoding/json"
|
||||
// "math/rand"
|
||||
// "time"
|
||||
// "net"
|
||||
|
||||
// "github.com/azukaar/cosmos-server/src/utils"
|
||||
// )
|
||||
|
||||
// func DeviceConfig(w http.ResponseWriter, req *http.Request) {
|
||||
// time.Sleep(time.Duration(rand.Float64()*2)*time.Second)
|
||||
|
||||
// if(req.Method == "GET") {
|
||||
|
||||
// ip, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
// if err != nil {
|
||||
// http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
|
||||
// // get authorization header
|
||||
// auth := req.Header.Get("Authorization")
|
||||
// if auth == "" {
|
||||
// http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
// return
|
||||
// }
|
||||
|
||||
// // remove "Bearer " from auth header
|
||||
// auth = strings.Replace(auth, "Bearer ", "", 1)
|
||||
|
||||
|
||||
|
||||
// } else {
|
||||
// utils.Error("DeviceConfig: Method not allowed" + req.Method, nil)
|
||||
// utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
// return
|
||||
// }
|
||||
// }
|
182
src/constellation/api_devices_create.go
Normal file
182
src/constellation/api_devices_create.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
type DeviceCreateRequestJSON struct {
|
||||
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) {
|
||||
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)
|
||||
APIKey := utils.GenerateRandomString(32)
|
||||
|
||||
if utils.AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Log("ConstellationDeviceCreation: Creating Device " + deviceName)
|
||||
|
||||
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,
|
||||
"Blocked": false,
|
||||
}).Decode(&device)
|
||||
|
||||
if err2 == mongo.ErrNoDocuments {
|
||||
|
||||
cert, key, fingerprint, err := generateNebulaCert(deviceName, request.IP, request.PublicKey, false)
|
||||
|
||||
if err != nil {
|
||||
utils.Error("DeviceCreation: Error while creating Device", err)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
http.StatusInternalServerError, "DC001")
|
||||
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
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.Error("DeviceCreation: Error while getting fingerprint", err)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
http.StatusInternalServerError, "DC007")
|
||||
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,
|
||||
"Fingerprint": fingerprint,
|
||||
"APIKey": APIKey,
|
||||
"Blocked": false,
|
||||
})
|
||||
|
||||
if err3 != nil {
|
||||
utils.Error("DeviceCreation: Error while creating Device", err3)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
http.StatusInternalServerError, "DC004")
|
||||
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
|
||||
}
|
||||
|
||||
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, APIKey, utils.ConstellationDevice{
|
||||
Nickname: nickname,
|
||||
DeviceName: deviceName,
|
||||
PublicKey: key,
|
||||
IP: request.IP,
|
||||
IsLighthouse: request.IsLighthouse,
|
||||
IsRelay: request.IsRelay,
|
||||
PublicHostname: request.PublicHostname,
|
||||
Port: request.Port,
|
||||
APIKey: APIKey,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
utils.Error("DeviceCreation: Error while reading config", err)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
http.StatusInternalServerError, "DC005")
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": map[string]interface{}{
|
||||
"Nickname": nickname,
|
||||
"DeviceName": deviceName,
|
||||
"PublicKey": key,
|
||||
"PrivateKey": cert,
|
||||
"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 {
|
||||
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
|
||||
}
|
||||
}
|
18
src/constellation/api_devices_index.go
Normal file
18
src/constellation/api_devices_index.go
Normal 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
|
||||
}
|
||||
}
|
73
src/constellation/api_devices_list.go
Normal file
73
src/constellation/api_devices_list.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
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.ConstellationDevice
|
||||
|
||||
// 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
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
// Respond with the list of devices
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": devices,
|
||||
})
|
||||
}
|
99
src/constellation/api_nebula.go
Normal file
99
src/constellation/api_nebula.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
func API_GetConfig(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
// read utils.CONFIGFOLDER + "nebula.yml"
|
||||
config, err := ioutil.ReadFile(utils.CONFIGFOLDER + "nebula.yml")
|
||||
|
||||
if err != nil {
|
||||
utils.Error("SettingGet: error while reading nebula.yml", err)
|
||||
utils.HTTPError(w, "Error while reading nebula.yml", http.StatusInternalServerError, "HTTP002")
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": string(config),
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func API_Restart(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
RestartNebula()
|
||||
|
||||
utils.Log("Constellation: nebula restarted")
|
||||
|
||||
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_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
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
logs, err := os.ReadFile(utils.CONFIGFOLDER+"nebula.log")
|
||||
if err != nil {
|
||||
utils.Error("Error reading file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": string(logs),
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
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.DNSDisabled = true
|
||||
// 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
|
||||
}
|
||||
}
|
62
src/constellation/index.go
Normal file
62
src/constellation/index.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
var err error
|
||||
|
||||
// if date is > 1st of January 2024
|
||||
timeNow := time.Now()
|
||||
if timeNow.Year() > 2024 || (timeNow.Year() == 2024 && timeNow.Month() > 1) {
|
||||
utils.Error("Constellation: this preview version has expired, please update to use the lastest version of Constellation.", nil)
|
||||
// disable constellation
|
||||
configFile := utils.ReadConfigFromFile()
|
||||
configFile.ConstellationConfig.Enabled = false
|
||||
utils.SetBaseMainConfig(configFile)
|
||||
return
|
||||
}
|
||||
|
||||
// if Constellation is enabled
|
||||
if utils.GetMainConfig().ConstellationConfig.Enabled {
|
||||
if !utils.GetMainConfig().ConstellationConfig.SlaveMode {
|
||||
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().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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
542
src/constellation/nebula.go
Normal file
542
src/constellation/nebula.go
Normal file
|
@ -0,0 +1,542 @@
|
|||
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"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"github.com/natefinch/lumberjack"
|
||||
)
|
||||
|
||||
var logBuffer *lumberjack.Logger
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
logBuffer = &lumberjack.Logger{
|
||||
Filename: utils.CONFIGFOLDER+"nebula.log",
|
||||
MaxSize: 1, // megabytes
|
||||
MaxBackups: 1,
|
||||
MaxAge: 15, //days
|
||||
Compress: false,
|
||||
}
|
||||
|
||||
process = exec.Command(binaryToRun(), "-config", utils.CONFIGFOLDER+"nebula.yml")
|
||||
|
||||
// Set up multi-writer for stderr
|
||||
process.Stderr = io.MultiWriter(logBuffer, os.Stderr)
|
||||
|
||||
if utils.LoggingLevelLabels[utils.GetMainConfig().LoggingLevel] == utils.DEBUG {
|
||||
// Set up multi-writer for stdout if in debug mode
|
||||
process.Stdout = io.MultiWriter(logBuffer, os.Stdout)
|
||||
} else {
|
||||
process.Stdout = io.MultiWriter(logBuffer)
|
||||
}
|
||||
|
||||
// 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 nil
|
||||
}
|
||||
|
||||
if err := process.Process.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
process = nil
|
||||
utils.Log("Stopped nebula.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func RestartNebula() {
|
||||
stop()
|
||||
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
|
||||
}
|
||||
|
||||
config := utils.ReadConfigFromFile()
|
||||
config.ConstellationConfig.Enabled = false
|
||||
config.ConstellationConfig.SlaveMode = false
|
||||
config.ConstellationConfig.DNSDisabled = false
|
||||
|
||||
utils.SetBaseMainConfig(config)
|
||||
|
||||
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,
|
||||
"Blocked": false,
|
||||
})
|
||||
cursor.All(nil, &devices)
|
||||
|
||||
if err != nil {
|
||||
return []utils.ConstellationDevice{}, err
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func GetBlockedDevices() ([]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{}{
|
||||
"Blocked": 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
|
||||
|
||||
if !overwriteConfig.PrivateNode {
|
||||
finalConfig.StaticHostMap = map[string][]string{
|
||||
"192.168.201.1": []string{
|
||||
utils.GetMainConfig().ConstellationConfig.ConstellationHostname + ":4242",
|
||||
},
|
||||
}
|
||||
} else {
|
||||
finalConfig.StaticHostMap = map[string][]string{}
|
||||
}
|
||||
|
||||
// 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 blocked devices
|
||||
blockedDevices, err := GetBlockedDevices()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, d := range blockedDevices {
|
||||
finalConfig.PKI.Blocklist = append(finalConfig.PKI.Blocklist, d.Fingerprint)
|
||||
}
|
||||
|
||||
finalConfig.Lighthouse.AMLighthouse = !overwriteConfig.PrivateNode
|
||||
|
||||
// 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 {
|
||||
finalConfig.Relay.Relays = append(finalConfig.Relay.Relays, cleanIp(l.IP))
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal the combined config to YAML
|
||||
yamlData, err := yaml.Marshal(finalConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete nebula.yml if exists
|
||||
if _, err := os.Stat(outputPath); err == nil {
|
||||
os.Remove(outputPath)
|
||||
}
|
||||
|
||||
// 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, capki, cert, key, APIKey string, device utils.ConstellationDevice) (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
|
||||
}
|
||||
|
||||
lh, err := GetAllLightHouses()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if staticHostMap, ok := configMap["static_host_map"].(map[interface{}]interface{}); ok {
|
||||
if !utils.GetMainConfig().ConstellationConfig.PrivateNode {
|
||||
staticHostMap["192.168.201.1"] = []string{
|
||||
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
|
||||
if lighthouseMap, ok := configMap["lighthouse"].(map[interface{}]interface{}); ok {
|
||||
lighthouseMap["am_lighthouse"] = device.IsLighthouse
|
||||
|
||||
lighthouseMap["hosts"] = []string{}
|
||||
if !utils.GetMainConfig().ConstellationConfig.PrivateNode {
|
||||
lighthouseMap["hosts"] = append(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")
|
||||
}
|
||||
|
||||
if pkiMap, ok := configMap["pki"].(map[interface{}]interface{}); ok {
|
||||
pkiMap["ca"] = capki
|
||||
pkiMap["cert"] = cert
|
||||
pkiMap["key"] = key
|
||||
} else {
|
||||
return "", errors.New("pki not found in nebula.yml")
|
||||
}
|
||||
|
||||
if relayMap, ok := configMap["relay"].(map[interface{}]interface{}); ok {
|
||||
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["constellation_device_name"] = name
|
||||
configMap["constellation_local_dns_overwrite"] = true
|
||||
configMap["constellation_local_dns_overwrite_address"] = "192.168.201.1"
|
||||
configMap["constellation_public_hostname"] = device.PublicHostname
|
||||
configMap["constellation_api_key"] = APIKey
|
||||
|
||||
// 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 GetCertFingerprint(certPath string) (string, error) {
|
||||
// nebula-cert print -json
|
||||
var cmd *exec.Cmd
|
||||
|
||||
cmd = exec.Command(binaryToRun() + "-cert",
|
||||
"print",
|
||||
"-json",
|
||||
"-path", certPath,
|
||||
)
|
||||
|
||||
// capture and parse output
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
utils.Error("Error while printing cert", err)
|
||||
}
|
||||
|
||||
var certInfo map[string]interface{}
|
||||
err = json.Unmarshal(output, &certInfo)
|
||||
if err != nil {
|
||||
utils.Error("Error while unmarshalling cert information", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Extract fingerprint, replace "fingerprint" with the actual key where the fingerprint is stored in the JSON output
|
||||
fingerprint, ok := certInfo["fingerprint"].(string)
|
||||
if !ok {
|
||||
utils.Error("Fingerprint not found or not a string", nil)
|
||||
return "", errors.New("fingerprint not found or not a string")
|
||||
}
|
||||
|
||||
return fingerprint, nil
|
||||
}
|
||||
|
||||
func generateNebulaCert(name, ip, PK string, saveToFile bool) (string, string, string, error) {
|
||||
// Run the nebula-cert command
|
||||
var cmd *exec.Cmd
|
||||
|
||||
if(PK == "") {
|
||||
cmd = exec.Command(binaryToRun() + "-cert",
|
||||
"sign",
|
||||
"-ca-crt", utils.CONFIGFOLDER + "ca.crt",
|
||||
"-ca-key", utils.CONFIGFOLDER + "ca.key",
|
||||
"-name", name,
|
||||
"-ip", ip,
|
||||
)
|
||||
} else {
|
||||
// write PK to temp.cert
|
||||
err := ioutil.WriteFile("./temp.key", []byte(PK), 0644)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("failed to write temp.key: %s", err)
|
||||
}
|
||||
cmd = exec.Command(binaryToRun() + "-cert",
|
||||
"sign",
|
||||
"-ca-crt", utils.CONFIGFOLDER + "ca.crt",
|
||||
"-ca-key", utils.CONFIGFOLDER + "ca.key",
|
||||
"-name", name,
|
||||
"-ip", ip,
|
||||
"-in-pub", "./temp.key",
|
||||
)
|
||||
// delete temp.key
|
||||
defer os.Remove("./temp.key")
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
fingerprint, err := GetCertFingerprint(certPath)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("failed to get certificate fingerprint: %s", err)
|
||||
}
|
||||
|
||||
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), fingerprint, 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
|
||||
}
|
113
src/constellation/nebula_default.go
Normal file
113
src/constellation/nebula_default.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
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"`
|
||||
Blocklist []string `yaml:"blocklist"`
|
||||
}{
|
||||
CA: utils.CONFIGFOLDER + "ca.crt",
|
||||
Cert: utils.CONFIGFOLDER + "cosmos.crt",
|
||||
Key: utils.CONFIGFOLDER + "cosmos.key",
|
||||
Blocklist: []string{},
|
||||
},
|
||||
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"`
|
||||
Respond bool `yaml:"respond"`
|
||||
}{
|
||||
Punch: true,
|
||||
Respond: true,
|
||||
},
|
||||
Relay: struct {
|
||||
AMRelay bool `yaml:"am_relay"`
|
||||
UseRelays bool `yaml:"use_relays"`
|
||||
Relays []string `yaml:"relays"`
|
||||
}{
|
||||
AMRelay: true,
|
||||
UseRelays: true,
|
||||
Relays: []string{},
|
||||
},
|
||||
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: []utils.NebulaFirewallRule {
|
||||
utils.NebulaFirewallRule {
|
||||
Host: "any",
|
||||
Port: "any",
|
||||
Proto: "any",
|
||||
},
|
||||
},
|
||||
Inbound: []utils.NebulaFirewallRule {
|
||||
utils.NebulaFirewallRule {
|
||||
Host: "any",
|
||||
Port: "any",
|
||||
Proto: "any",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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,13 @@ func InitServer() *mux.Router {
|
|||
srapi.HandleFunc("/api/background", UploadBackground)
|
||||
srapi.HandleFunc("/api/background/{ext}", GetBackground)
|
||||
|
||||
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)
|
||||
srapi.HandleFunc("/api/constellation/block", constellation.DeviceBlock)
|
||||
|
||||
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
|
||||
srapi.Use(utils.EnsureHostname)
|
||||
|
|
|
@ -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,9 @@ func main() {
|
|||
|
||||
authorizationserver.Init()
|
||||
|
||||
constellation.InitDNS()
|
||||
|
||||
constellation.Init()
|
||||
|
||||
StartServer()
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ type marketGetResult struct {
|
|||
}
|
||||
|
||||
func MarketGet(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
if utils.LoggedInOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,6 @@ func MarketGet(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// return the first 10 results of each market
|
||||
marketGetResult := marketGetResult{
|
||||
All: make(map[string]interface{}),
|
||||
Showcase: []appDefinition{},
|
||||
|
|
|
@ -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,16 +76,40 @@ 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")
|
||||
// hide hostname (dangerous)
|
||||
// req.Header.Del("Host")
|
||||
|
||||
hostname := utils.GetMainConfig().HTTPConfig.Hostname
|
||||
if route.Host != "" && route.UseHost {
|
||||
hostname = route.Host
|
||||
}
|
||||
if route.UsePathPrefix {
|
||||
hostname = hostname + route.PathPrefix
|
||||
}
|
||||
|
||||
hostDest := hostname
|
||||
if route.OverwriteHostHeader != "" {
|
||||
hostDest = route.OverwriteHostHeader
|
||||
}
|
||||
|
||||
// hide hostname (dangerous)
|
||||
// req.Header.Set("Host", url.Host)
|
||||
// req.Host = url.Host
|
||||
|
||||
req.Header.Set("Host", hostDest)
|
||||
req.Host = hostDest
|
||||
|
||||
if VerboseForwardHeader {
|
||||
req.Header.Set("X-Origin-Host", url.Host)
|
||||
req.Header.Set("Host", url.Host)
|
||||
req.Header.Set("X-Origin-Host", hostDest)
|
||||
req.Header.Set("X-Forwarded-Host", hostDest)
|
||||
req.Header.Set("X-Forwarded-For", utils.GetClientIP(req))
|
||||
req.Header.Set("X-Real-IP", utils.GetClientIP(req))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if AcceptInsecureHTTPSTarget && url.Scheme == "https" {
|
||||
|
@ -100,8 +124,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 +148,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 {
|
||||
|
@ -85,6 +85,8 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination htt
|
|||
}
|
||||
}
|
||||
|
||||
destination = utils.Restrictions(route.RestrictToConstellation, route.WhitelistInboundIPs)(destination)
|
||||
|
||||
destination = SmartShieldMiddleware(route.Name, route.SmartShield)(destination)
|
||||
|
||||
originCORS := route.CORSOrigin
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"encoding/json"
|
||||
|
@ -189,13 +190,25 @@ func logOutUser(w http.ResponseWriter, req *http.Request) {
|
|||
HttpOnly: true,
|
||||
}
|
||||
|
||||
clientCookie := http.Cookie{
|
||||
Name: "client-infos",
|
||||
Value: "{}",
|
||||
Expires: time.Now().Add(-time.Hour * 24 * 365),
|
||||
Path: "/",
|
||||
Secure: utils.IsHTTPS,
|
||||
HttpOnly: false,
|
||||
}
|
||||
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" {
|
||||
cookie.Domain = ""
|
||||
clientCookie.Domain = ""
|
||||
} else {
|
||||
cookie.Domain = "." + reqHostNoPort
|
||||
clientCookie.Domain = "." + reqHostNoPort
|
||||
}
|
||||
|
||||
http.SetCookie(w, &cookie)
|
||||
http.SetCookie(w, &clientCookie)
|
||||
|
||||
// TODO: logout every other device if asked by increasing passwordcycle
|
||||
}
|
||||
|
@ -254,13 +267,24 @@ func SendUserToken(w http.ResponseWriter, req *http.Request, user utils.User, mf
|
|||
HttpOnly: true,
|
||||
}
|
||||
|
||||
clientCookie := http.Cookie{
|
||||
Name: "client-infos",
|
||||
Value: user.Nickname + "," + strconv.Itoa(int(user.Role)),
|
||||
Expires: expiration,
|
||||
Path: "/",
|
||||
Secure: utils.IsHTTPS,
|
||||
HttpOnly: false,
|
||||
}
|
||||
|
||||
utils.Log("UserLogin: Setting cookie for " + reqHostNoPort)
|
||||
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" {
|
||||
cookie.Domain = ""
|
||||
clientCookie.Domain = ""
|
||||
} else {
|
||||
if utils.IsValidHostname(reqHostNoPort) {
|
||||
cookie.Domain = "." + reqHostNoPort
|
||||
clientCookie.Domain = "." + reqHostNoPort
|
||||
} else {
|
||||
utils.Error("UserLogin: Invalid hostname", nil)
|
||||
utils.HTTPError(w, "User Logging Error", http.StatusInternalServerError, "UL001")
|
||||
|
@ -269,4 +293,5 @@ func SendUserToken(w http.ResponseWriter, req *http.Request, user utils.User, mf
|
|||
}
|
||||
|
||||
http.SetCookie(w, &cookie)
|
||||
http.SetCookie(w, &clientCookie)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -260,4 +258,60 @@ func IsValidHostname(hostname string) bool {
|
|||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IPInRange(ipStr, cidrStr string) (bool, error) {
|
||||
_, cidrNet, err := net.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("parse CIDR range: %w", err)
|
||||
}
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false, fmt.Errorf("parse IP: invalid IP address")
|
||||
}
|
||||
|
||||
return cidrNet.Contains(ip), nil
|
||||
}
|
||||
|
||||
func Restrictions(RestrictToConstellation bool, WhitelistInboundIPs []string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
isUsingWhiteList := len(WhitelistInboundIPs) > 0
|
||||
|
||||
isInWhitelist := false
|
||||
isInConstellation := strings.HasPrefix(ip, "192.168.201.") || strings.HasPrefix(ip, "192.168.202.")
|
||||
|
||||
for _, ipRange := range WhitelistInboundIPs {
|
||||
if strings.Contains(ipRange, "/") {
|
||||
if ok, _ := IPInRange(ip, ipRange); ok {
|
||||
isInWhitelist = true
|
||||
}
|
||||
} else {
|
||||
if ip == ipRange {
|
||||
isInWhitelist = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isInConstellationPassing := !RestrictToConstellation || isInConstellation
|
||||
isWhitelistPassing := !isUsingWhiteList || isInWhitelist
|
||||
|
||||
// check if the request is coming from the constellation IP range 192.168.201.0/24
|
||||
if (!isInConstellationPassing && !isWhitelistPassing) {
|
||||
Log("Request from " + ip + " is blocked because of restrictions isInConstellationPassing: " + fmt.Sprintf("%v", isInConstellationPassing) + " and isWhitelistPassing: " + fmt.Sprintf("%v", isWhitelistPassing))
|
||||
http.Error(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -90,6 +90,7 @@ type Config struct {
|
|||
MarketConfig MarketConfig
|
||||
HomepageConfig HomepageConfig
|
||||
ThemeConfig ThemeConfig
|
||||
ConstellationConfig ConstellationConfig
|
||||
}
|
||||
|
||||
type HomepageConfig struct {
|
||||
|
@ -180,6 +181,9 @@ type ProxyRouteConfig struct {
|
|||
DisableHeaderHardening bool
|
||||
VerboseForwardHeader bool
|
||||
AddionalFilters []AddionalFiltersConfig
|
||||
RestrictToConstellation bool
|
||||
OverwriteHostHeader string
|
||||
WhitelistInboundIPs []string
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
|
@ -205,4 +209,115 @@ type MarketConfig struct {
|
|||
type MarketSource struct {
|
||||
Name string
|
||||
Url string
|
||||
}
|
||||
}
|
||||
|
||||
type ConstellationConfig struct {
|
||||
Enabled bool
|
||||
SlaveMode bool
|
||||
PrivateNode bool
|
||||
DNSDisabled bool
|
||||
DNSPort string
|
||||
DNSFallback string
|
||||
DNSBlockBlacklist bool
|
||||
DNSAdditionalBlocklists []string
|
||||
CustomDNSEntries []ConstellationDNSEntry
|
||||
NebulaConfig NebulaConfig
|
||||
ConstellationHostname string
|
||||
}
|
||||
|
||||
type ConstellationDNSEntry struct {
|
||||
Type string
|
||||
Key string
|
||||
Value 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"`
|
||||
Blocked bool `json:"blocked"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
APIKey string `json:"-"`
|
||||
}
|
||||
|
||||
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"`
|
||||
Blocklist []string `yaml:"blocklist"`
|
||||
} `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"`
|
||||
Respond bool `yaml:"respond"`
|
||||
} `yaml:"punchy"`
|
||||
|
||||
Relay struct {
|
||||
AMRelay bool `yaml:"am_relay"`
|
||||
UseRelays bool `yaml:"use_relays"`
|
||||
Relays []string `yaml:"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"`
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ var ReBootstrapContainer func(string) error
|
|||
|
||||
var LetsEncryptErrors = []string{}
|
||||
|
||||
var CONFIGFOLDER = "/config/"
|
||||
|
||||
var DefaultConfig = Config{
|
||||
LoggingLevel: "INFO",
|
||||
NewInstall: true,
|
||||
|
@ -60,6 +62,17 @@ var DefaultConfig = Config{
|
|||
Sources: []MarketSource{
|
||||
},
|
||||
},
|
||||
ConstellationConfig: ConstellationConfig{
|
||||
Enabled: false,
|
||||
DNSDisabled: false,
|
||||
DNSFallback: "8.8.8.8:53",
|
||||
DNSAdditionalBlocklists: []string{
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||
"https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-only/hosts",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func FileExists(path string) bool {
|
||||
|
@ -193,10 +206,23 @@ 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"
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -219,7 +245,7 @@ func GetConfigFileName() string {
|
|||
configFile := os.Getenv("CONFIG_FILE")
|
||||
|
||||
if configFile == "" {
|
||||
configFile = "/config/cosmos.config.json"
|
||||
configFile = CONFIGFOLDER + "cosmos.config.json"
|
||||
}
|
||||
|
||||
return configFile
|
||||
|
@ -539,10 +565,33 @@ func GetNetworkUsage() NetworkStatus {
|
|||
return NetworkStatus{}
|
||||
}
|
||||
|
||||
func DownloadFile(url string) (string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func GetClientIP(req *http.Request) string {
|
||||
/*ip := req.Header.Get("X-Forwarded-For")
|
||||
if ip == "" {
|
||||
ip = 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