This commit is contained in:
Mauro Condarelli 2021-11-10 18:15:17 +03:00 committed by GitHub
commit e989661f1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 556 additions and 15 deletions

6
.gitignore vendored
View file

@ -7,6 +7,8 @@ temp
.env.test.local
.env.production.local
.idea
# Created by https://www.toptal.com/developers/gitignore/api/vscode,yarn,react,node
# Edit at https://www.toptal.com/developers/gitignore?templates=vscode,yarn,react,node
@ -156,9 +158,9 @@ sketch
# if you are NOT using Zero-installs, then:
# comment the following lines
!.yarn/cache
#!.yarn/cache
# and uncomment the following lines
# .pnp.*
.pnp.*
# End of https://www.toptal.com/developers/gitignore/api/vscode,yarn,react,node

48
backend/.gitignore vendored
View file

@ -33,6 +33,54 @@ jspm_packages/
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Misc
.DS_Store
.env.local

View file

@ -4,6 +4,7 @@ const axios = require("axios");
const api = require("../utils/controller-api");
const db = require("../utils/db");
const constants = require("../utils/constants");
const zns = require("../utils/zns");
async function getNetworkAdditionalData(data) {
let additionalData = db
@ -21,11 +22,30 @@ async function getNetworkAdditionalData(data) {
delete data.remoteTraceLevel;
delete data.remoteTraceTarget;
//let ad = { ...additionalData.value() };
let ad_ = additionalData.value();
let ad = JSON.parse(JSON.stringify(ad_));
data.dns = {
domain: ad_.dnsDomain,
servers: [],
};
if (ad_.dnsIP) data.dns["servers"].push(ad_.dnsIP);
console.log(
`*** ad_="${JSON.stringify(ad_, null, 3)}" -> ad="${JSON.stringify(
ad,
null,
3
)}" -> ${JSON.stringify(data.dns, null, 3)}`
);
delete ad.dnsIP;
delete ad.dnsDomain;
delete ad.dnsEnable;
delete ad.dnsWildcard;
return {
id: data.id,
type: "Network",
clock: Math.floor(new Date().getTime() / 1000),
...additionalData.value(),
...ad,
config: data,
};
}
@ -46,13 +66,11 @@ async function getNetworksData(nwids) {
return [];
});
let data = Promise.all(
return Promise.all(
multipleRes.map((el) => {
return getNetworkAdditionalData(el.data);
})
);
return data;
}
exports.createNetworkAdditionalData = createNetworkAdditionalData;
@ -62,6 +80,9 @@ async function createNetworkAdditionalData(nwid) {
additionalConfig: {
description: "",
rulesSource: constants.defaultRulesSource,
dnsEnable: false,
dnsDomain: "",
dnsWildcard: false,
},
members: [],
};
@ -79,6 +100,21 @@ async function updateNetworkAdditionalData(nwid, data) {
if (data.hasOwnProperty("rulesSource")) {
additionalData.rulesSource = data.rulesSource;
}
if (data.hasOwnProperty("dnsEnable")) {
if (data.dnsEnable) {
//TODO: start ZeroNSd and get its IP address
additionalData.dnsIP = "127.0.0.1";
} else {
additionalData.dnsIP = null;
}
additionalData.dnsEnable = data.dnsEnable;
}
if (data.hasOwnProperty("dnsDomain")) {
additionalData.dnsDomain = data.dnsDomain;
}
if (data.hasOwnProperty("dnsWildcard")) {
additionalData.dnsWildcard = data.dnsWildcard;
}
if (additionalData) {
db.get("networks")
@ -86,6 +122,10 @@ async function updateNetworkAdditionalData(nwid, data) {
.map("additionalConfig")
.map((additionalConfig) => _.assign(additionalConfig, additionalData))
.write();
if (data.hasOwnProperty("dnsEnable")) {
zns.handleNet(db.get("networks").filter({ id: nwid }).value()[0]);
}
}
}

147
backend/utils/zns.js Normal file
View file

@ -0,0 +1,147 @@
const cp = require("child_process");
const path = require("path");
const fs = require("fs");
const db = require("../utils/db");
//TODO: does this kind of "optimization" make sense in Node.js?
let token = null;
function getToken() {
if (!token)
try {
token = db.get("users").value()[0].token;
} catch {
console.warn("*** token retrieval failed");
}
return token;
}
function setPid(nwid, pid) {
db.get("networks")
.filter({ id: nwid })
.map("additionalConfig")
.map((additionalConfig) => (additionalConfig.pidDNS = pid))
.write();
}
const isRunning = (query, pid) => {
return new Promise(function (resolve) {
//FIXME: Check if pgrep is available
cp.exec(`pgrep ${query}`, (err, stdout) => {
resolve(stdout.indexOf(`${pid}`) > -1);
});
});
};
function startDNS(token, nwid, conf) {
//FIXME: check it does the right thing when conf.pidDNS is null/undefined
isRunning("zeronsd", conf.pidDNS).then((ok) => {
if (ok) {
console.log(
`startDNS(${token}, ${nwid}): already active on PID ${conf.pidDNS}`
);
} else {
let cmd = "zeronsd";
let opts = Array();
if (process.geteuid() === 0) {
// production in Docker container
} else {
// we are debugging
let myLocal = "/home/mcon/.cargo/bin";
let pth = process.env.PATH.split(path.delimiter);
if (!pth.includes(myLocal)) pth.push(myLocal);
if (
!process.env.PATH.split(path.delimiter).some(function (d) {
let e = path.resolve(d, cmd);
console.log(`*** PATH testing: "${d}" -> "${e}"`);
try {
fs.accessSync(e, fs.constants.X_OK);
console.log(" is executable");
cmd = "sudo";
opts.push("-E", e);
return true;
} catch (e) {
console.warn(" cannot execute");
return false;
}
})
) {
console.error(`*** zeronsd not found in PATH (${process.env.PATH})`);
return;
}
}
opts.push("start");
if (conf.hasOwnProperty("dnsWildcard") && conf.dnsWildcard) {
opts.push("-w");
}
if (conf.hasOwnProperty("dnsDomain") && !!conf.dnsDomain) {
opts.push("-d", conf.dnsDomain);
}
opts.push(nwid);
process.env.ZEROTIER_CENTRAL_TOKEN = token;
console.log(`*** PATH: "${process.env.PATH}"`);
console.log(
`*** ZEROTIER_CENTRAL_TOKEN: "${process.env.ZEROTIER_CENTRAL_TOKEN}"`
);
let dns = cp.spawn(cmd, opts, { detached: true });
dns.on("spawn", () => {
console.log(
`zeronsd successfully spawned [${dns.pid}](${dns.spawnargs})`
);
setPid(nwid, dns.pid);
});
dns.stdout.on("data", (data) => {
console.log(`zeronsd spawn stdout: ${data}`);
});
dns.stderr.on("data", (data) => {
console.error(`zeronsd spawn stderr: ${data}`);
});
dns.on("error", (error) => {
console.log(`zeronsd spawn ERROR: [${error}](${dns.spawnargs})`);
});
dns.on("close", (code) => {
console.log(`zeronsd exited: [${code}](${dns.spawnargs})`);
setPid(nwid, null);
});
}
});
}
function stopDNS(nwid, conf) {
let pid = conf.pidDNS;
if (pid) {
isRunning("zeronsd", pid).then((ok) => {
if (ok) {
console.log(`stopDNS(${nwid}): stopping PID ${pid}`);
try {
process.kill(pid);
} catch (e) {
console.error(`stopDNS(${nwid}): stopping PID ${pid} FAILED (${e})`);
}
} else {
console.log(`stopDNS(${nwid}): PID ${pid} is stale`);
}
});
setPid(nwid, null);
} else {
console.log(`stopDNS(${nwid}): net has no PID`);
}
}
exports.handleNet = handleNet;
function handleNet(net) {
let cfg = net.additionalConfig;
if (cfg.dnsEnable) {
startDNS(getToken(), net.id, cfg);
} else {
stopDNS(net.id, cfg);
}
}
exports.scan = scan;
function scan() {
let nets = db.get("networks").value();
nets.forEach((net) => {
handleNet(net);
});
}

View file

@ -0,0 +1,81 @@
# ---- initialize build stage
FROM node:current-alpine as builder
# ---- build ZeroTier-One
RUN apk add --update --no-cache alpine-sdk linux-headers \
&& git clone --quiet https://github.com/zerotier/ZeroTierOne.git /src \
&& make -C /src -f make-linux.mk
# ---- build Zero-UI
ENV INLINE_RUNTIME_CHUNK=false
ENV GENERATE_SOURCEMAP=false
RUN yarn set version berry
WORKDIR /app/frontend
COPY ./frontend/package*.json /app/frontend
COPY ./frontend/yarn.lock /app/frontend
RUN yarn install
COPY ./frontend /app/frontend
RUN yarn build
# ---- build ZeroNSd \
FROM rust:alpine as rbuild
ARG IS_LOCAL=0
ARG VERSION=main
ARG IS_TAG=0
RUN apk add --update --no-cache git libressl-dev musl-dev \
&& git clone https://github.com/zerotier/zeronsd.git \
&& cd zeronsd \
&& sh cargo-docker.sh
# ---- initialize deploy stage
FROM node:current-alpine
LABEL description="ZeroTier One as Docker Image"
LABEL org.opencontainers.image.authors="mcondarelli@soft-in.com"
# ---- copy ZeroTier-One
ARG ZT_VERSION
LABEL version="${ZT_VERSION}"
RUN apk add --update --no-cache libc6-compat libstdc++
COPY --from=builder /src/zerotier-one /usr/sbin/
RUN mkdir -p /var/lib/zerotier-one \
&& ln -s /usr/sbin/zerotier-one /usr/sbin/zerotier-idtool \
&& ln -s /usr/sbin/zerotier-one /usr/sbin/zerotier-cli
EXPOSE 9993/udp
# ---- copy Zero-UI
WORKDIR /app/frontend/build
COPY --from=builder /app/frontend/build /app/frontend/build/
WORKDIR /app/backend
COPY ./backend/package*.json /app/backend
COPY ./backend/yarn.lock /app/backend
RUN yarn install
COPY ./backend /app/backend
EXPOSE 4000
ENV NODE_ENV=production
ENV ZU_SECURE_HEADERS=true
ENV ZU_SERVE_FRONTEND=true
# ---- copy ZeroNSd
COPY --from=rbuild /usr/local/cargo/bin/zeronsd /usr/sbin/
# ---- final setup
VOLUME /var/lib/zerotier
COPY docker/all-in-one/entrypoint.sh /entrypoint.sh
RUN chmod 755 /entrypoint.sh
CMD /entrypoint.sh

View file

@ -0,0 +1,75 @@
# ---- initialize build stage
FROM node:current-bullseye as builder
# ---- build ZeroTier-One
RUN apt-get update && apt-get install -y build-essential \
&& git clone --quiet https://github.com/zerotier/ZeroTierOne.git /src \
&& make -C /src -f make-linux.mk
# ---- build Zero-UI
ENV INLINE_RUNTIME_CHUNK=false
ENV GENERATE_SOURCEMAP=false
RUN yarn set version berry
WORKDIR /app/frontend
COPY ./frontend/package*.json /app/frontend
COPY ./frontend/yarn.lock /app/frontend
RUN yarn install
COPY ./frontend /app/frontend
RUN yarn build
# ---- build ZeroNSd \
FROM rust:bullseye as rbuild
ARG IS_LOCAL=0
ARG VERSION=main
ARG IS_TAG=0
COPY ./docker/all-in-one/zeronsd.patch /tmp/zeronsd.patch
RUN apt-get update && apt-get install --no-install-recommends -y git libssl-dev \
&& git clone https://github.com/zerotier/zeronsd.git \
&& cd zeronsd \
&& patch -p 1 </tmp/zeronsd.patch \
&& sh cargo-docker.sh
# ---- initialize deploy stage
FROM node:current-bullseye
LABEL description="ZeroTier One as Docker Image"
LABEL org.opencontainers.image.authors="mcondarelli@soft-in.com"
# ---- copy ZeroTier-One
ARG ZT_VERSION
LABEL version="${ZT_VERSION}"
COPY --from=builder /src/zerotier-one /usr/sbin/
RUN mkdir -p /var/lib/zerotier-one \
&& ln -s /usr/sbin/zerotier-one /usr/sbin/zerotier-idtool \
&& ln -s /usr/sbin/zerotier-one /usr/sbin/zerotier-cli
EXPOSE 9993/udp
# ---- copy Zero-UI
WORKDIR /app/frontend/build
COPY --from=builder /app/frontend/build /app/frontend/build/
WORKDIR /app/backend
COPY ./backend/package*.json /app/backend
COPY ./backend/yarn.lock /app/backend
RUN yarn install
COPY ./backend /app/backend
EXPOSE 4000
ENV NODE_ENV=production
ENV ZU_SECURE_HEADERS=false
ENV ZU_SERVE_FRONTEND=true
ENV ZU_DATAPATH=/var/lib/zerotier-one/db.json
# ---- copy ZeroNSd
COPY --from=rbuild /usr/local/cargo/bin/zeronsd /usr/sbin/
RUN apt-get update && apt-get install --no-install-recommends -y ca-certificates curl
# ---- final setup
VOLUME /var/lib/zerotier-one
COPY docker/all-in-one/entrypoint.sh /entrypoint.sh
RUN chmod 755 /entrypoint.sh
CMD /entrypoint.sh

View file

@ -0,0 +1,32 @@
#!/bin/sh
grepzt() {
(find /proc -name exe | xargs -I{} readlink {}) 2>/dev/null | grep -q zerotier-one
return $?
}
echo "starting zerotier"
setsid /usr/sbin/zerotier-one &
while ! grepzt
do
echo "zerotier hasn't started, waiting a second"
sleep 1
done
echo "joining networks"
for i in "$@"
do
echo "joining $i"
while ! zerotier-cli join "$i"
do
echo "joining $i failed; trying again in 1s"
sleep 1
done
done
echo "starting node"
node ./bin/www
echo "at end"

View file

@ -0,0 +1,12 @@
diff --git a/src/utils.rs b/src/utils.rs
index 0f62d52..0f90e69 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -21,6 +21,7 @@ pub(crate) fn central_config(token: String) -> Configuration {
let mut config = Configuration::default();
config.user_agent = Some(version());
config.bearer_access_token = Some(token);
+ config.base_path = "http://localhost:3000/api".to_string();
return config;
}

48
frontend/.gitignore vendored
View file

@ -9,6 +9,54 @@ coverage
# Production
build
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Misc
.DS_Store
.env.local

View file

@ -8,6 +8,7 @@ import {
Typography,
TextField,
Select,
List,
} from "@material-ui/core";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
@ -27,19 +28,24 @@ function NetworkSettings({ network, setNetwork }) {
}
};
const handleChange = (key1, key2, mode = "text", additionalData = null) => (
event
) => {
const value = parseValue(event, mode, additionalData);
const handleChange =
(key1, key2, mode = "text", additionalData = null) =>
(event) => {
const value = parseValue(event, mode, additionalData);
let updatedNetwork = replaceValue({ ...network }, key1, key2, value);
setNetwork(updatedNetwork);
let updatedNetwork = replaceValue({ ...network }, key1, key2, value);
setNetwork(updatedNetwork);
let data = setValue({}, key1, key2, value);
let data = setValue({}, key1, key2, value);
sendReq(data);
};
sendReq(data);
};
console.log(
`*** dns="${JSON.stringify(network)}" -> ${JSON.stringify(
network["config"]
)}`
);
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@ -89,6 +95,56 @@ function NetworkSettings({ network, setNetwork }) {
<option value={true}>Private</option>
<option value={false}>Public</option>
</Select>
<Divider />
<Typography>ZeroDNS setup</Typography>
<List
style={{
display: "flex",
flexDirection: "row",
}}
>
<Grid item>
<Checkbox
checked={network["dnsEnable"]}
color="primary"
onChange={handleChange("dnsEnable", null, "checkbox")}
/>
<span>Enable DNS</span>
</Grid>
<Divider
orientation="vertical"
style={{
margin: "10px",
}}
flexItem
/>
<Grid item>
<TextField
value={network["config"]["dns"]["domain"]}
onChange={handleChange("dnsDomain")}
label="Domain"
variant="filled"
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
<Divider
orientation="vertical"
style={{
margin: "10px",
}}
flexItem
/>
<Grid item>
<Checkbox
checked={network["dnsWildcard"]}
color="primary"
onChange={handleChange("dnsWildcard", null, "checkbox")}
/>
<span>Use wildcards</span>
</Grid>
</List>
</Grid>
<Divider />
<Grid item>