From 431f886f2bd915c25235a80ffa91b328a40a3834 Mon Sep 17 00:00:00 2001 From: Marc Olivier Bergeron Date: Fri, 1 Aug 2025 11:22:10 -0400 Subject: [PATCH] Implemented the CTF folder initialize command. --- .deploy/track.yaml | 1 - ctf/__init__.py | 9 +- ctf/__main__.py | 46 +++++++- ctf/templates/.deploy/cleanup.yaml | 135 ++++++++++++++++++++++ ctf/templates/.deploy/common.yaml | 14 +++ ctf/templates/.deploy/common/dns.tf | 6 + ctf/templates/.deploy/common/variables.tf | 13 +++ ctf/templates/.deploy/common/versions.tf | 9 ++ pyproject.toml | 2 +- 9 files changed, 225 insertions(+), 10 deletions(-) delete mode 100644 .deploy/track.yaml create mode 100644 ctf/templates/.deploy/cleanup.yaml create mode 100644 ctf/templates/.deploy/common.yaml create mode 100644 ctf/templates/.deploy/common/dns.tf create mode 100644 ctf/templates/.deploy/common/variables.tf create mode 100644 ctf/templates/.deploy/common/versions.tf diff --git a/.deploy/track.yaml b/.deploy/track.yaml deleted file mode 100644 index 0967ef4..0000000 --- a/.deploy/track.yaml +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/ctf/__init__.py b/ctf/__init__.py index e40fa6d..c9b61a7 100644 --- a/ctf/__init__.py +++ b/ctf/__init__.py @@ -71,8 +71,6 @@ def find_ctf_root_directory() -> str: while path != (path := os.path.dirname(p=path)): dir = os.listdir(path=path) - if ".git" not in dir: - continue if ".deploy" not in dir: continue if "challenges" not in dir: @@ -82,7 +80,7 @@ def find_ctf_root_directory() -> str: if path == "/": if "CTF_ROOT_DIR" not in os.environ: LOG.critical( - msg='Could not automatically find the root directory nor the "CTF_ROOT_DIR" environment variable.' + msg='Could not automatically find the root directory nor the "CTF_ROOT_DIR" environment variable. To initialize a new root directory, use `ctf init [path]`' ) exit(1) return os.environ.get("CTF_ROOT_DIR", default=".") @@ -91,4 +89,7 @@ def find_ctf_root_directory() -> str: return path -CTF_ROOT_DIRECTORY = find_ctf_root_directory() +if len(sys.argv) > 1 and sys.argv[1] == "init": + CTF_ROOT_DIRECTORY = os.path.join(os.getcwd(), ".") +else: + CTF_ROOT_DIRECTORY = find_ctf_root_directory() diff --git a/ctf/__main__.py b/ctf/__main__.py index 8c6a996..7ed7112 100644 --- a/ctf/__main__.py +++ b/ctf/__main__.py @@ -92,6 +92,31 @@ def terraform_binary() -> str: return path +def init(args: argparse.Namespace) -> None: + if os.path.isdir(os.path.join(args.path, "challenges")) or os.path.isdir( + os.path.join(args.path, ".deploy") + ): + LOG.error(f"Directory {args.path} is already initialized.") + exit(code=1) + + challenge_dir = os.path.join(args.path, "challenges") + deploy_dir = os.path.join(args.path, ".deploy") + + try: + os.mkdir(challenge_dir) + LOG.info(f"Created {challenge_dir}") + shutil.copytree(os.path.join(TEMPLATES_ROOT_DIRECTORY, ".deploy"), deploy_dir) + LOG.info(f"Created {deploy_dir}") + except Exception: + import traceback + + if os.path.isdir(challenge_dir): + os.rmdir(challenge_dir) + if os.path.isdir(deploy_dir): + shutil.rmtree(deploy_dir) + LOG.critical(traceback.format_exc()) + + def new(args: argparse.Namespace) -> None: LOG.info(msg=f"Creating a new track: {args.name}") if not re.match(pattern=r"^[a-z][a-z0-9\-]{0,61}[a-z0-9]$", string=args.name): @@ -1310,6 +1335,18 @@ def main(): help="Script version.", ) + parser_init = subparsers.add_parser( + "init", + help="Initialize a folder with the default CTF structure.", + ) + parser_init.set_defaults(func=init) + parser_init.add_argument( + "path", + nargs="?", + default=CTF_ROOT_DIRECTORY, + help="Initialize the folder at the given path.", + ) + parser_flags = subparsers.add_parser( "flags", help="Get flags from tracks", @@ -1556,10 +1593,11 @@ def main(): ENV["INCUS_REMOTE"] = args.remote if not os.path.isdir(s=(p := os.path.join(CTF_ROOT_DIRECTORY, "challenges"))): - LOG.error( - msg=f"Directory `{p}` not found. Make sure this script is ran from the root directory OR set the CTF_ROOT_DIR environment variable to the root directory." - ) - exit(code=1) + if args.func.__name__ != "init": + LOG.error( + msg=f"Directory `{p}` not found. Make sure this script is ran from the root directory OR set the CTF_ROOT_DIR environment variable to the root directory." + ) + exit(code=1) args.func(args=args) diff --git a/ctf/templates/.deploy/cleanup.yaml b/ctf/templates/.deploy/cleanup.yaml new file mode 100644 index 0000000..95a7a28 --- /dev/null +++ b/ctf/templates/.deploy/cleanup.yaml @@ -0,0 +1,135 @@ +- name: Pre-deployment system cleanup + hosts: all + order: shuffle + gather_facts: false + any_errors_fatal: true + + tasks: + - name: Make network configuration static + ansible.builtin.shell: | + [ ! -e /run/systemd/resolve/resolv.conf ] && exit 0 + rm -f /etc/resolv.conf || true + cat /run/systemd/resolve/resolv.conf > /etc/resolv.conf + when: 'nsec_production | default(False)' + changed_when: true + + - name: Mask most systemd units + ansible.builtin.shell: | + for i in \ + apt-daily-upgrade.service \ + apt-daily-upgrade.timer \ + apt-daily.service \ + apt-daily.timer \ + console-getty.service \ + console-setup.service \ + dmesg.service \ + dpkg-db-backup.service \ + dpkg-db-backup.timer \ + e2scrub_all.service \ + e2scrub_all.timer \ + e2scrub_reap.service \ + emergency.service \ + fstrim.service \ + fstrim.timer \ + getty-static.service \ + getty@tty1.service \ + initrd-cleanup.service \ + initrd-parse-etc.service \ + initrd-switch-root.service \ + initrd-udevadm-cleanup-db.service \ + keyboard-setup.service \ + kmod-static-nodes.service \ + ldconfig.service \ + logrotate.service \ + logrotate.timer \ + modprobe@configfs.service \ + modprobe@dm_mod.service \ + modprobe@drm.service \ + modprobe@fuse.service \ + modprobe@loop.service \ + motd-news.service \ + motd-news.timer \ + netplan-ovs-cleanup.service \ + rescue.service \ + rsyslog.service \ + setvtrgb.service \ + syslog.socket \ + systemd-ask-password-console.service \ + systemd-ask-password-wall.service \ + systemd-battery-check.service \ + systemd-bsod.service \ + systemd-confext.service \ + systemd-fsck-root.service \ + systemd-fsckd.service \ + systemd-fsckd.socket \ + systemd-hibernate-resume.service \ + systemd-initctl.service \ + systemd-initctl.socket \ + systemd-journal-catalog-update.service \ + systemd-journal-flush.service \ + systemd-journald-dev-log.socket \ + systemd-journald.service \ + systemd-journald.socket \ + systemd-pcrextend.socket \ + systemd-pcrlock-file-system.service \ + systemd-pcrlock-firmware-code.service \ + systemd-pcrlock-firmware-config.service \ + systemd-pcrlock-machine-id.service \ + systemd-pcrlock-make-policy.service \ + systemd-pcrlock-secureboot-authority.service \ + systemd-pcrlock-secureboot-policy.service \ + systemd-pcrmachine.service \ + systemd-pcrphase-initrd.service \ + systemd-pcrphase-sysinit.service \ + systemd-pcrphase.service \ + systemd-random-seed.service \ + systemd-repart.service \ + systemd-soft-reboot.service \ + systemd-sysctl.service \ + systemd-sysext.service \ + systemd-sysext.socket \ + systemd-sysupdate-reboot.service \ + systemd-sysupdate-reboot.timer \ + systemd-sysupdate.service \ + systemd-sysupdate.timer \ + systemd-sysusers.service \ + systemd-timesyncd.service \ + systemd-tpm2-setup-early.service \ + systemd-tpm2-setup.service \ + systemd-update-done.service \ + systemd-update-utmp-runlevel.service \ + systemd-update-utmp.service \ + ua-reboot-cmds.service \ + ua-timer.service \ + ua-timer.timer \ + ubuntu-advantage.service; do + ln -s /dev/null /etc/systemd/system/${i} || true + done + changed_when: true + + - name: Mask network systemd units + ansible.builtin.shell: | + for i in \ + networkd-dispatcher.service \ + systemd-network-generator.service \ + systemd-networkd-wait-online.service \ + systemd-networkd.service \ + systemd-networkd.socket \ + systemd-resolved.service \ + systemd-udev-settle.service \ + systemd-udev-trigger.service \ + systemd-udevd-control.socket \ + systemd-udevd-kernel.socket \ + systemd-udevd.service; do + ln -s /dev/null /etc/systemd/system/${i} || true + done + when: 'nsec_production | default(False)' + changed_when: true + + - name: Remove all cron jobs + ansible.builtin.shell: | + rm -f /etc/cron.*/* || true + changed_when: true + + - name: Reboot the instance + ansible.builtin.reboot: diff --git a/ctf/templates/.deploy/common.yaml b/ctf/templates/.deploy/common.yaml new file mode 100644 index 0000000..34ca777 --- /dev/null +++ b/ctf/templates/.deploy/common.yaml @@ -0,0 +1,14 @@ +- name: Pre-deployment Common + hosts: all + order: shuffle + gather_facts: false + any_errors_fatal: true + + tasks: + - name: Distro update and Python3 install + ansible.builtin.raw: | + apt update && apt upgrade -y && apt install -y python3 + changed_when: true + +- name: Importing cleanup.yaml Playbook + ansible.builtin.import_playbook: cleanup.yaml diff --git a/ctf/templates/.deploy/common/dns.tf b/ctf/templates/.deploy/common/dns.tf new file mode 100644 index 0000000..6ae54c7 --- /dev/null +++ b/ctf/templates/.deploy/common/dns.tf @@ -0,0 +1,6 @@ +resource "incus_network_zone" "this" { + remote = var.incus_remote + + name = "ctf" + description = "DNS zone for the internal .ctf TLD" +} diff --git a/ctf/templates/.deploy/common/variables.tf b/ctf/templates/.deploy/common/variables.tf new file mode 100644 index 0000000..920a2bf --- /dev/null +++ b/ctf/templates/.deploy/common/variables.tf @@ -0,0 +1,13 @@ +variable "incus_remote" { + default = "local" + type = string +} + +variable "deploy" { + default = "dev" + type = string +} + +locals { + track = yamldecode(file("${path.module}/../track.yaml")) +} diff --git a/ctf/templates/.deploy/common/versions.tf b/ctf/templates/.deploy/common/versions.tf new file mode 100644 index 0000000..e2a285a --- /dev/null +++ b/ctf/templates/.deploy/common/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">=1.5.7" + required_providers { + incus = { + source = "lxc/incus" + version = ">=0.1.3" + } + } +} diff --git a/pyproject.toml b/pyproject.toml index 2515af9..4e8ef7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "black", "tabulate==0.9.0", ] -version = "1.1.3" +version = "1.1.4" classifiers = [ "Programming Language :: Python :: 3", "Operating System :: OS Independent",