First real commit, everything implemented

This commit is contained in:
Josiah Baldwin
2024-11-20 15:23:03 -08:00
parent 69afbfeba7
commit 5c20a2b8fb
36 changed files with 3625 additions and 282 deletions

View File

@@ -0,0 +1,108 @@
import os
import base64
import subprocess
import time
import json
import atexit
import pytest
import requests
thisdir = os.path.abspath(os.path.dirname(__file__))
# os.environ["BUILDKIT_PROGRESS"] = "plain"
USERNAMES = ["admin", "privileged", "unprivileged"]
global users
users = None
def create_env():
global users
if users is not None:
return users
users = {}
for username in USERNAMES:
password = base64.b64encode(os.urandom(24)).decode()
users[username] = password
with open(os.path.join(thisdir, "scripts", "meshcentral", "users.json"), "w") as outfile:
json.dump(users, outfile)
return users
global _docker_process
_docker_process = None
class Agent(object):
def __init__(self, meshid, mcurl, clienturl, dockerurl):
self.meshid = meshid
self._mcurl = mcurl
self._clienturl = clienturl
self._dockerurl = dockerurl
r = requests.post(f"{self._clienturl}/add-agent", json={"url": f"{self._dockerurl}", "meshid": self.meshid})
self.nodeid = r.json()["id"]
def __enter__(self):
return self
def __exit__(self, exc_t, exc_v, exc_tb):
try:
requests.post("{self._clienturl}/remove-agent/{self.nodeid}")
except:
pass
class TestEnvironment(object):
def __init__(self):
self.users = create_env()
self._subp = None
self.mcurl = "wss://localhost:8086"
self.clienturl = "http://localhost:5000"
self._dockerurl = "host.docker.internal:8086"
def __enter__(self):
global _docker_process
if _docker_process is not None:
self._subp = _docker_process
return self
self._subp = _docker_process = subprocess.Popen(["docker", "compose", "up", "--build", "--force-recreate", "--no-deps"], stdout=subprocess.DEVNULL, cwd=thisdir)
timeout = 30
start = time.time()
while time.time() - start < timeout:
try:
data = subprocess.check_output(["docker", "inspect", "meshctrl-meshcentral", "--format='{{json .State.Health}}'"], cwd=thisdir, stderr=subprocess.DEVNULL)
# docker outputs for humans, not computers. This is the easiest way to chop off the ends
data = json.loads(data.strip()[1:-1])
except Exception as e:
time.sleep(1)
continue
try:
if data["Status"] == "healthy":
break
except:
pass
time.sleep(1)
else:
self.__exit__(None, None, None)
raise Exception("Failed to create docker instance")
return self
def __exit__(self, exc_t, exc_v, exc_tb):
pass
def create_agent(self, meshid):
return Agent(meshid, self.mcurl, self.clienturl, self._dockerurl)
def _kill_docker_process():
if _docker_process is not None:
_docker_process.kill()
subprocess.run(["docker", "compose", "down"], cwd=thisdir)
atexit.register(_kill_docker_process)
@pytest.fixture(scope="session")
def env():
with TestEnvironment() as e:
yield e
if __name__ == "__main__":
with TestEnvironment() as env:
input("it's up")

View File

@@ -0,0 +1,18 @@
FROM python:3.12
WORKDIR /usr/local/app
# Install the application dependencies
# COPY requirements.txt ./
# Copy in the source code
COPY scripts/client ./scripts
RUN pip install --no-cache-dir -r ./scripts/requirements.txt
EXPOSE 5000
# Setup an app user so the container doesn't run as the root user
RUN useradd app
USER app
WORKDIR /usr/local/app/scripts
CMD ["python3", "-m", "flask", "--app", "agent_server", "run", "--host=0.0.0.0", "--debug"]

View File

@@ -0,0 +1,52 @@
networks:
meshctrl:
driver: bridge
services:
client:
restart: unless-stopped
container_name: meshctrl-client
image: client
build:
dockerfile: client.dockerfile
ports:
- 5000:5000
depends_on:
- meshcentral
environment:
TZ: US/LosAngeles
# volumes:
# # mongodb data-directory - A must for data persistence
# - ./meshcentral/mongodb_data:/data/db
networks:
- meshctrl
extra_hosts:
- "host.docker.internal:host-gateway"
meshcentral:
restart: always
container_name: meshctrl-meshcentral
# use the official meshcentral container
image: meshcentral
build:
dockerfile: meshcentral.dockerfile
ports:
# MeshCentral will moan and try everything not to use port 80, but you can also use it if you so desire, just change the config.json according to your needs
- 8086:443
environment:
TZ: US/LosAngeles
#volumes:
# config.json and other important files live here. A must for data persistence
#- ./meshcentral/data:/opt/meshcentral/meshcentral-data
# where file uploads for users live
#- ./meshcentral/user_files:/opt/meshcentral/meshcentral-files
# location for the meshcentral-backups - this should be mounted to an external storage
#- ./meshcentral/backup:/opt/meshcentral/meshcentral-backups
# location for site customization files
#- ./meshcentral/web:/opt/meshcentral/meshcentral-web
networks:
- meshctrl
healthcheck:
test: curl -k --fail https://localhost:443/ || exit 1
interval: 5s
timeout: 120s

View File

@@ -0,0 +1,7 @@
FROM ghcr.io/ylianst/meshcentral:latest
RUN apk add curl
RUN apk add python3
WORKDIR /opt/meshcentral/
COPY ./scripts/meshcentral ./scripts
COPY ./meshcentral/data /opt/meshcentral/meshcentral-data
CMD ["python3", "/opt/meshcentral/scripts/create_users.py"]

View File

@@ -0,0 +1,38 @@
{
"$schema": "https://raw.githubusercontent.com/Ylianst/MeshCentral/master/meshcentral-config-schema.json",
"settings": {
"plugins":{"enabled": false},
"_mongoDb": null,
"cert": "host.docker.internal",
"_WANonly": true,
"_LANonly": true,
"port": 443,
"aliasPort": 8086,
"redirPort": 80,
"_redirAliasPort": 80,
"AgentPong": 300,
"TLSOffload": false,
"SelfUpdate": false,
"AllowFraming": false,
"WebRTC": false,
"interUserMessaging": true
},
"domains": {
"": {
"_title": "MyServer",
"_title2": "Servername",
"minify": true,
"NewAccounts": false,
"localSessionRecording": false,
"_userNameIsEmail": true,
"_certUrl": "my.reverse.proxy",
"allowedOrigin": true
}
},
"_letsencrypt": {
"__comment__": "Requires NodeJS 8.x or better, Go to https://letsdebug.net/ first before>",
"_email": "myemail@mydomain.com",
"_names": "myserver.mydomain.com",
"production": false
}
}

View File

@@ -0,0 +1,68 @@
from flask import Flask, json, request
import requests
import tempfile
import base64
import os
import subprocess
import time
AGENT_URL_TEMPLATE = "https://{}/meshagents?id=6"
SETTINGS_URL_TEMPLATE = "https://{}/meshsettings?id={}"
api = Flask(__name__)
agents = {}
# if not os.path.exists(os.path.join(mesh_dir, "meshagent")):
# os.makedirs(meshtemp)
# subprocess.check_call(["wget", AGENT_URL, "-O", os.path.join(meshtemp, "meshagent")])
# subprocess.check_call(["wget", SETTINGS_URL, "-O", os.path.join(meshtemp, "meshagent.msh")])
# subprocess.check_call(["chmod", "+x", os.path.join(meshtemp, "meshagent")])
# shutil.copytree(meshtemp, mesh_dir)
# subprocess.check_call(["chown", "-R", f"{user}:{user}", mesh_dir])
@api.route('/add-agent', methods=['POST'])
def add_agent():
api.logger.info("text")
AGENT_URL = AGENT_URL_TEMPLATE.format(request.json["url"])
SETTINGS_URL = SETTINGS_URL_TEMPLATE.format(request.json["url"], request.json["meshid"])
d = tempfile.mkdtemp()
agent_path = os.path.join(d, "meshagent")
msh_path = os.path.join(d, "meshagent.msh")
with open(agent_path, "wb") as outfile:
for chunk in requests.get(AGENT_URL, stream=True, verify=False).iter_content(chunk_size=16*1024):
outfile.write(chunk)
with open(msh_path, "wb") as outfile:
for chunk in requests.get(SETTINGS_URL, stream=True, verify=False).iter_content(chunk_size=16*1024):
outfile.write(chunk)
os.chmod(agent_path, 0o0777)
os.chmod(msh_path, 0o0777)
# Generates a certificate if we don't got one
subprocess.call([agent_path, "-connect"])
agent_hex = subprocess.check_output([agent_path, '-exec', "console.log(require('_agentNodeId')());process.exit()"]).strip().decode()
agent_id = base64.b64encode(bytes.fromhex(agent_hex)).decode().replace("+", "@").replace("/", "$")
p = subprocess.Popen(["stdbuf", "-o0", agent_path, "connect"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=d)
agents[agent_id] = {"process": p, "id": agent_id}
text = ""
start = time.time()
while time.time() - start < 5:
text += p.stdout.read1().decode("utf-8")
api.logger.info(text)
if "Connected." in text:
break
time.sleep(.1)
else:
raise Exception(f"Failed to start agent: {text}")
return {"id": agent_id}
@api.route('/remove-agent/<agentid>', methods=['POST'])
def remove_agent(agentid):
agents[agentid]["process"].kill()
return ""
@api.route('/', methods=['GET'])
def slash():
return [_["id"] for _ in agents]
if __name__ == '__main__':
api.run()

View File

@@ -0,0 +1,2 @@
flask
requests

View File

@@ -0,0 +1,15 @@
import os
import subprocess
import json
thisdir = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(thisdir, "users.json")) as infile:
users = json.load(infile)
for username, password in users.items():
subprocess.check_output(["node", "/opt/meshcentral/meshcentral", "--createaccount", username, "--pass", password, "--name", username])
subprocess.check_output(["node", "/opt/meshcentral/meshcentral", "--adminaccount", "admin"])
subprocess.call(["bash", "/opt/meshcentral/startup.sh"])

View File

@@ -0,0 +1 @@
{"admin": "3U6zP4iIes5ISH15XxjYLjJcCdw9jU0m", "privileged": "aiIO0zLMGsU7++FYVDNxhlpYlZ1andRB", "unprivileged": "Cz9OMV1wkVd9pXdWi4lkBAAu6TMt43MA"}