Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Genisys server component for listening for new clients to come online and provision them with Ansible playbooks #53

Merged
merged 22 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions documentation/Initialization Process.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<mxfile host="app.diagrams.net" modified="2024-02-09T18:39:56.405Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0" etag="JeeHfBx3YnPunmTjQv58" version="23.1.2" type="github">
<diagram name="Page-1" id="Q2wZ2sweRgpwyFX9r32k">
<mxGraphModel dx="1103" dy="582" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="XiOSTXteVxpvsbVn6G1l-1" value="Genisys Server" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="10" y="10" width="400" height="30" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-3" value="Client" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="440" y="10" width="400" height="30" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-5" value="DHCP/TFTP" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="290" y="50" width="120" height="20" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-7" value="" style="endArrow=none;dashed=1;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="640" y="350" as="sourcePoint" />
<mxPoint x="640" y="40" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-8" value="" style="endArrow=none;dashed=1;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" target="XiOSTXteVxpvsbVn6G1l-5">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="350" y="120" as="sourcePoint" />
<mxPoint x="450" y="270" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-9" value="" style="shape=mxgraph.sysml.x;" vertex="1" parent="1">
<mxGeometry x="340" y="110" width="20" height="20" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-11" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="350" y="90" as="sourcePoint" />
<mxPoint x="640" y="90" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-12" value="PXE Boot" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="XiOSTXteVxpvsbVn6G1l-11">
<mxGeometry x="-0.0805" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-13" value="FTP" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="150" y="50" width="120" height="20" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-14" value="" style="endArrow=none;dashed=1;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" target="XiOSTXteVxpvsbVn6G1l-13">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="160" as="sourcePoint" />
<mxPoint x="450" y="260" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-15" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="640" y="140" as="sourcePoint" />
<mxPoint x="210" y="140" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-16" value="Download Firstboot Files" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="XiOSTXteVxpvsbVn6G1l-15">
<mxGeometry x="-0.624" y="-1" relative="1" as="geometry">
<mxPoint x="-79" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-17" value="" style="shape=mxgraph.sysml.x;" vertex="1" parent="1">
<mxGeometry x="200" y="150" width="20" height="20" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-18" value="HTTPd" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="10" y="50" width="120" height="20" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-19" value="" style="endArrow=none;dashed=1;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" target="XiOSTXteVxpvsbVn6G1l-18">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="70" y="320" as="sourcePoint" />
<mxPoint x="310" y="260" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-20" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="640" y="200" as="sourcePoint" />
<mxPoint x="70" y="200" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-21" value="Send &quot;Hello&quot;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="XiOSTXteVxpvsbVn6G1l-20">
<mxGeometry x="-0.624" y="-1" relative="1" as="geometry">
<mxPoint x="-79" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-24" value="Reboot" style="whiteSpace=wrap;html=1;shape=partialRectangle;top=0;left=0;bottom=1;right=0;points=[[0,1],[1,1]];fillColor=none;align=center;verticalAlign=bottom;routingCenterY=0.5;snapToPoint=1;recursiveResize=0;autosize=1;treeFolding=1;treeMoving=1;newEdgeStyle={&quot;edgeStyle&quot;:&quot;entityRelationEdgeStyle&quot;,&quot;startArrow&quot;:&quot;none&quot;,&quot;endArrow&quot;:&quot;none&quot;,&quot;segment&quot;:10,&quot;curved&quot;:1,&quot;sourcePerimeterSpacing&quot;:0,&quot;targetPerimeterSpacing&quot;:0};" vertex="1" parent="1">
<mxGeometry x="680" y="120" width="80" height="30" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-25" value="" style="edgeStyle=entityRelationEdgeStyle;startArrow=none;endArrow=none;segment=10;curved=1;sourcePerimeterSpacing=0;targetPerimeterSpacing=0;rounded=0;" edge="1" target="XiOSTXteVxpvsbVn6G1l-24" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="640" y="170" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-26" value="Ansible" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="150" y="220" width="120" height="20" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-27" value="" style="endArrow=none;dashed=1;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" target="XiOSTXteVxpvsbVn6G1l-26">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="320" as="sourcePoint" />
<mxPoint x="80" y="80" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-28" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="70" y="260" as="sourcePoint" />
<mxPoint x="210" y="260" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-29" value="Update Inventory" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="XiOSTXteVxpvsbVn6G1l-28">
<mxGeometry x="0.1768" y="1" relative="1" as="geometry">
<mxPoint x="-22" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-30" value="" style="shape=mxgraph.sysml.x;" vertex="1" parent="1">
<mxGeometry x="60" y="310" width="20" height="20" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-31" value="" style="shape=mxgraph.sysml.x;" vertex="1" parent="1">
<mxGeometry x="200" y="310" width="20" height="20" as="geometry" />
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-37" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="290" as="sourcePoint" />
<mxPoint x="640" y="290" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XiOSTXteVxpvsbVn6G1l-38" value="Run Playbooks" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="XiOSTXteVxpvsbVn6G1l-37">
<mxGeometry x="0.1768" y="1" relative="1" as="geometry">
<mxPoint x="-22" as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
45 changes: 0 additions & 45 deletions documentation/example.ini

This file was deleted.

41 changes: 41 additions & 0 deletions documentation/example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,33 @@ Network:
ftp:
directory: "/ftp"
ftp-port: 20
server:
# the prefered port, server will throw an exception if port is occupied on
# the specified ip above. If no IP was specifed, attempts to bind to all interfaces
port: 15206
# the user and group the server should drop privileges to if ran as root
# if absent will run the server as the running user
# if not ran as root or the specified user, a warning will be emitted but the server will still run
# if group is absent, will attempt to use a group with the same name as user
user: genisys
group: genisys
# the working directory for the server process. Used to canonicalize any paths from the ansible section.
# The specified user needs, at minimum, execute (--x) access to the directory. If any of the ansible files
# are stored there the user will need read (r--) permissions on those files or read-write (rw-) access for
# the inventory file. They will also need write (-w-) access to the working-directory if the inventory file
# does not exist. Defaults to the user's home directory
working-directory: /srv/genisys
# run the server using SSL
# either both or neither of cert and key need specified if enabled
# if neither are present, genisys generates a self-signed key on first run
# the self-signed cert will be created at $GENISYS_CERT_STORE or /etc/genisys/ssl/
# if the ssl section is missing, the server will be ran without ssl
ssl:
cert: /path/to/fullchain.pem
key: /path/to/privkey.pem
# nessecary if the privkey is encrypted, the first line of the file will be used as the passphrase
# when decrytping the key, after stripping any newline
password-file: /path/to/key/password
OS:
os: "debian"
version-name: bookworm
Expand All @@ -35,9 +62,23 @@ Users:
- "test2.pub"
sudoer: true
Applications:
- curl
DNSMasq Overrides:
authoritative: false
Scripts:
script-dir: "/scripts"
move-all: true
script-list: ["script1.sh"]
# configure settings for the ansible integration
ansible:
# this specifies which inventory file is updated by the server process
# is created if it does not exist. If it does exist, must be writable by the server user.
# If the inventory file already exists, it should be defined in YAML format. A 'genisys'
# section will be added to the file if it does not already exist.
inventory: /var/genisys/inventory
# the ssh private key used to run the playbooks. The corresponding public key should be specified
# in the Users section above. Must be readable by the server user
ssh-key: /etc/genisys/ssh/id_rsa
# list of paths to playbooks to run when the server receives a HELLO
playbooks:
- /etc/genisys/playbooks/firstrun.yaml
15 changes: 6 additions & 9 deletions genisys/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from genisys.modules.firstboot.hello import Hello
from genisys.modules.firstboot.service import Service
import genisys.config_parser as cp
import genisys.server

MODULES = [
OSDownload,
Expand Down Expand Up @@ -57,12 +58,6 @@ def generate_config(file, root="."):
mod = module(file)
mod.install(root)

def daemon():
"""Monitor the config file for changes"""
print("Starting daemon...")

raise NotImplementedError

def run(subcommand, args):
"""Parse command line options and run the relevant helper method"""
# Config Parser
Expand All @@ -74,6 +69,8 @@ def run(subcommand, args):
install_config(yaml_parser, args.root)
elif subcommand == "generate":
generate_config(yaml_parser, args.root)
elif subcommand == "server":
genisys.server.run(yaml_parser)


def main():
Expand All @@ -92,12 +89,12 @@ def main():
generate_parser = subparsers.add_parser(
"generate", help="Generate the configuration files."
)
daemon_parser = subparsers.add_parser(
"daemon", help="Monitor the config file for changes."
server_parser = subparsers.add_parser(
"server", help="Run the server to listen for new clients."
)

# Flags for all subparsers
for subparser in [validate_parser, install_parser, generate_parser]:
for subparser in [validate_parser, install_parser, generate_parser, server_parser]:
subparser.add_argument(
"-f",
"--file",
Expand Down
81 changes: 81 additions & 0 deletions genisys/server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import ssl
import pwd
import grp
import os
import sys
from warnings import warn
from signal import signal, SIGTERM
from typing_extensions import Dict, TypedDict, cast
from genisys.config_parser import YAMLParser
from genisys.server.inventory import Inventory
from genisys.server.http import GenisysHTTPServer, GenisysHTTPRequestHandler
import genisys.server.tls

DEFAULT_PORT = 15206
DEFAULT_INVENTORY = "/etc/ansible/hosts"

ServerOptions = TypedDict("ServerOptions", {
"port": int,
"user": str,
"group": str,
"working-directory": str,
"ssl": Dict[str, str]
})

def run(config: YAMLParser):
"""Drops priviledges, creates the server (with SSL, if applicable), then waits for requests"""
# parse config
network = config.get_section("Network")
server_options = cast(ServerOptions, network.get("server", {}) or {})

# drop priviledges
try:
server_user = drop_priviledges(server_options)
except PermissionError:
warn("Unable to drop privledges to the specified user. Continuing as current user.")
server_user = pwd.getpwuid(os.getuid())

# change working directory
workdir = server_options.get("working-directory", server_user.pw_dir)
os.chdir(workdir)

# install additional data for the server to use
ansible_cfg = config.get_section("ansible")
inventory_path = ansible_cfg.get("inventory", DEFAULT_INVENTORY)

# create a server
server_address = network.get('ip', '')
server_port = server_options.get("port", DEFAULT_PORT)
httpd = GenisysHTTPServer((server_address, server_port), Inventory(inventory_path), config)

# apply TLS if applicable
if 'ssl' in server_options:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
ssl_cert = genisys.server.tls.get_keychain(server_options['ssl'] or {})
ssl_context.load_cert_chain(**ssl_cert)
httpd.socket = ssl_context.wrap_socket(httpd.socket)


# run until SIGTERM is caught
def sigterm_handler(*_):
print("killed")
del httpd.inventory
sys.exit(SIGTERM)
signal(SIGTERM, sigterm_handler)
httpd.serve_forever()

def drop_priviledges(config: ServerOptions) -> pwd.struct_passwd:
"""Attempts to drop the priviledges to that of the specified users, returns false on failure"""
if 'user' not in config:
warn("No user specified. Continuing as current user.")
return pwd.getpwuid(os.geteuid())

grpnam = config.get('group', config['user'])
uid = pwd.getpwnam(config['user'])
gid = grp.getgrnam(grpnam)

os.setuid(uid.pw_uid)
os.setgid(gid.gr_gid)
return uid
Loading
Loading