ctfcli

repository·master·Indexed 42 days ago

https://github.com/ctfd/ctfcli

A command-line interface tool for managing Capture The Flag (CTF) events and challenges on CTFd instances. Version 0.1.8 provides capabilities for automating challenge creation, deployment, and synchronization, including support for Docker, Git subtrees/subrepos, and CI integrations with GitHub and GitLab. Key features include challenge scaffolding from templates, linting, healthchecks, and mirroring remote CTFd challenges to a local filesystem.

Tokens
6.9K
Snippets
32
Records
43
Agent score
52%

What's inside ctfcli

  1. How ctfcli plugins work

    master
    A ctfcli plugin is an addition to the command line interface achieved through dynamic class modification. When ctfcli starts, it loads the load(commands) function from each module found in the plugin directory. This function is passed a dictionary containing the core command classes. By using types.MethodType, a plugin can inject new methods into these classes, which then become available as new subcommands in the CLI.
  2. Write a new ctfcli plugin

    master

    Plugins in ctfcli work by dynamically modifying existing command classes. To create one:

    1. Create a module: Create a new directory in your plugin directory containing an __init__.py file.
    2. Implement a load function: Your __init__.py must define a load(commands) function. This function receives a dictionary of pre-defined command classes.
    3. Modify commands: Use types.MethodType to attach new methods to the command classes provided in the commands dictionary. To add a command to the plugins group, access commands["plugins"] and attach your method to it.

    Note: The plugin interface is currently in alpha and subject to change.

    from types import MethodType
    
    # 1. Define the logic for your new command
    def my_new_command(self):
        print("Hello from the plugin!")
    
    # 2. Define the load function required by ctfcli
    def load(commands):
        # Access the 'plugins' command group
        plugins = commands["plugins"]
        # Attach the function as a method to the plugins class
        plugins.my_new_command = MethodType(my_new_command, plugins)
  3. How challenges are resolved from names or paths

    master

    When running commands like mirror, verify, or format, ctfcli uses internal logic to resolve the target challenge(s). Understanding this helps in providing the correct arguments.

    Resolution Logic for a single challenge:

    1. Path-based: If the argument ends in .yml, .yaml, or is ., it is treated as a direct filesystem path.
    2. Name-based: If it is a string that doesn't look like a path, ctfcli looks up the name in your project configuration (.ctf/config) and resolves it to a path relative to the project root.
    3. Default: If no challenge is specified, the tool defaults to the current working directory.

    Resolution Logic for all challenges:

    • The tool iterates through all challenge keys defined in your project configuration and resolves their corresponding challenge.yml files.
  4. Manage media in the local configuration file

    master

    The ctfcli manages media mappings within a [media] section of the local configuration file.

    • Adding: When a file is added, a new entry is created: local_path = /files/remote_location.
    • Removing: When a file is removed, the entry for that local_path is deleted from the [media] section.
    • Retrieving: The url command relies on the [media] section to resolve local paths to remote locations, and the [config] section's url key to construct the final absolute URL.
  5. Handle ProjectNotInitialized error

    master

    If you attempt to run a command outside of a directory containing a .ctf folder, the tool will raise a ProjectNotInitialized exception. The CLI entrypoint handles this by prompting the user:

    Outside of a ctfcli project, would you like to start a new project in this directory?

    If confirmed, it triggers the CTFCLI.init() workflow.

  6. Initialize a new ctfcli project

    master

    Use the init method to set up a new CTFd project directory. This process creates a .ctf directory, generates a config file, and optionally sets up Git and CI integrations (like GitHub or GitLab).

    Parameters:

    • directory (str | os.PathLike | None): The path where the project should be created. Defaults to the current working directory.
    • no_git (bool): If True, skips running git init.
    • no_commit (bool): If True, skips the initial git commit.
    • github (bool): If True, adds GitHub integration.
    • gitlab (bool): If True, adds GitLab integration.

    Behavior:

    • If a directory is provided and doesn't exist, it will be created.
    • It prompts the user for the CTFd instance URL and the CTFd Admin Access Token.
    • If no CI integration flags are passed, it prompts for a comma-separated list of available integrations.
    • It creates a .ctf/config file containing the provided URL and token.
    • If Git is not already initialized in the directory, it runs git init (unless no_git is set) and performs an initial commit of the configuration and integration files (unless no_commit is set).
    # Example of what the underlying logic does
    CTFCLI.init(
        directory="my_ctf_project",
        github=True,
        no_commit=False
    )
  7. Available ctfcli commands

    master

    The ctfcli tool provides several command groups for managing your CTFd instance and project files. These are exposed via the main CLI entrypoint:

    • challenge: Manage CTF challenges.
    • config: Manage project configuration.
    • instance: Manage CTFd instance settings.
    • pages: Manage CTFd pages.
    • media: Manage media assets.
    • plugins: Manage ctfcli plugins.
    • templates: Manage templates.

    Note: Commands are loaded and can be extended via the plugin system.

    COMMANDS = {
        "challenge": ChallengeCommand(),
        "config": ConfigCommand(),
        "pages": PagesCommand(),
        "plugins": PluginsCommand(),
        "templates": TemplatesCommand(),
        "instance": InstanceCommand(),
        "media": MediaCommand(),
        "cli": CTFCLI(),
    }