diff --git a/Dockerfile b/Dockerfile index 5db15ce..f352406 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ COPY requirements.txt /app RUN pip install -r requirements.txt COPY app.py /app -COPY runbooksolutions /app +COPY runbooksolutions /app/runbooksolutions # Define the command to run your application CMD [ "/start.sh" ] \ No newline at end of file diff --git a/README.md b/README.md index ca81a71..a9b3ec8 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,32 @@ Configuration maintained in a simple `config.ini` file consisting of the server_ [agent] server_url=http://192.168.1.197 # Note: Do NOT include a trailing slash on the server_url client_id=9ab55261-bfb7-4bb3-ad29-a6dbdbf8a5af # Device Code Grant client_id provided by the server +auth=True # To disable auth when not using with RunbookSolutions. ``` -### Creating a Keytab File +## Expected Server Responses +Due to the Agent's nature; it can easily be used by others outside of RunbookSolutions. -``` +To implement a backend for this agent you will need to provided the following endpoints. + +`GET /api/agent` for the agent to load information about itself. This endpoint also provides the agent with a list of PLUGIN_ID's that it needs to load. + +`GET /api/agent/plugin/{PLUGIN_ID}` for the agent to download plugins. This endpoint also provides details about commands the plugin provides. + +`GET /api/agent/tasks` for the agent to load tasks that it needs to run. Tasks include scheduled and one-off tasks to run; and will always present tasks until they are removed from the backend. This allows for the agent to restart without skipping task execution. + +Additional details can be found on the [Expected Server Responses](/docs/Responses.md) page. + +## Creating a Keytab File + +Some plugins may require authentication against your windows domain. + +The simplest way to acomplish this is by using the [Docker Kerberos Keytab Generator](https://github.com/simplesteph/docker-kerberos-get-keytab): + +```sh +cd agent docker run -it --rm \ - -v $(pwd):/output \ + -v $(pwd)/kerberos:/output \ -e PRINCIPAL= \ simplesteph/docker-kerberos-get-keytab ``` \ No newline at end of file diff --git a/config.ini b/config.ini index 4e629ec..55be0c2 100644 --- a/config.ini +++ b/config.ini @@ -1,3 +1,7 @@ [agent] -server_url=http://192.168.1.197 # Note: Do NOT include a trailing slash on the server_url -client_id=9ab55261-bfb7-4bb3-ad29-a6dbdbf8a5af # Device Code Grant client_id provided by the server \ No newline at end of file +# Note: Do NOT include a trailing slash on the server_url +server_url=http://192.168.1.197 +# Device Code Grant client_id provided by the server +client_id=9ab55261-bfb7-4bb3-ad29-a6dbdbf8a5af +# If we are required to preform Device Code Authentication +auth=True \ No newline at end of file diff --git a/docs/Responses.md b/docs/Responses.md index b154c49..c0690c1 100644 --- a/docs/Responses.md +++ b/docs/Responses.md @@ -1,6 +1,13 @@ # Responses From Backend -GET /api/agent +> Note: The backend server is expected to identify the agent making the request without any additional parameters being sent. By default RunbookSolutions achieves this using the OAuth Device Code process to retrieve an Access Token for authentication. +>> In non-NAT'ed environments; the backend server could use the IP address from the request to identify agents. + +## `GET /api/agent` +This endpoint provides the agent with basic information about itself along with a list of PLUGIN_ID's that the agent should have loaded. + +- The Team ID provided here is used to identify and group agents to specific groups on the backend. It is **required** even if not used. + ```json { "data": { @@ -14,7 +21,17 @@ GET /api/agent } ``` -GET /api/agent/plugin/{plugin_id} +## `GET /api/agent/plugin/{plugin_id}` +This endpoint provides the agent with individual plugins for the agent along with the corresponding commands the plugin makes available. + +> Note: It is important to note that two different versions of a plugin may be loaded by an agent. Commands are prefixed with the PLUGIN_ID to avoid collisions. + +- The `script` variable contains the code the agent will execute when required. +- The `hash` variable is the `SHA512` hash of the script; the agent will verify both the script variable as well as the file it creates to store the plugin. +- The `commands` variable contains contains the details of what function in the program to run for which command is provided. + +> Important: Both the `script` variable and the file written to disk must match the provided hash for the plugin to be loaded and run. + ```json { "data": { @@ -40,7 +57,15 @@ GET /api/agent/plugin/{plugin_id} } ``` -GET /api/agent/tasks +## `GET /api/agent/tasks` +The following endpoint provides the agent with details of what commands need to be run, when (if scheduled), and any arguments for said command. + +- The `command` variable must match one of the keys provided by the `plugin.commands` variable when downloading plugins. +- The `cron` variable is the cron formatted schedule for when the task runs, or `null` if it should only be run once. +- The `arguments` variable should be a JSON encoded string containg the arument name and values for the function to run. + +> Note: The agent uses the `task.id` to ensure tasks are not being duplicated into the schedule and queue. + ```json { "data": [ diff --git a/runbooksolutions/agent/Agent.py b/runbooksolutions/agent/Agent.py index 883c2af..33c884c 100644 --- a/runbooksolutions/agent/Agent.py +++ b/runbooksolutions/agent/Agent.py @@ -19,7 +19,11 @@ class Agent: def __init__(self, num_threads: int = 1) -> None: self.agentConfig = self.loadConfig() - self.auth = Auth(url=self.agentConfig.get('server_url'), client_id=self.agentConfig.get('client_id')) + self.auth = Auth( + url=self.agentConfig.get('server_url'), + client_id=self.agentConfig.get('client_id'), + enabled=self.agentConfig.get('auth') + ) self.api = API(auth=self.auth, url=self.agentConfig.get('server_url')) self.pluginManager = PluginManager(self.api) self.queue = Queue(num_threads, self.pluginManager) diff --git a/runbooksolutions/auth/Auth.py b/runbooksolutions/auth/Auth.py index 1f482c8..2b57216 100644 --- a/runbooksolutions/auth/Auth.py +++ b/runbooksolutions/auth/Auth.py @@ -8,14 +8,18 @@ class Auth: url: str client_id: str + enabled: bool = True deviceCode: DeviceCode = None accessToken: AccessToken = None - def __init__(self, url: str, client_id: str) -> None: - logging.debug("Starting Authentication") - + def __init__(self, url: str, client_id: str, enabled: bool = True) -> None: self.url = url self.client_id = client_id + self.enabled = enabled + + logging.debug("Starting Authentication") + if not self.enabled: + return self.deviceCode = DeviceCode.load_from_store() self.accessToken = AccessToken.load_from_store() @@ -32,6 +36,11 @@ def __init__(self, url: str, client_id: str) -> None: logging.debug("Finished Authentication") def getHeaders(self) -> dict: + if not self.enabled: + return { + 'Content-Type': 'application/json', + } + headers = { 'Authorization': f'Bearer {self.accessToken.getAccessToken()}', 'Content-Type': 'application/json',