Skip to content
This repository has been archived by the owner on Feb 15, 2024. It is now read-only.

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
sniper7kills committed Nov 27, 2023
1 parent e6df8d4 commit c48f21d
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<[email protected]> \
simplesteph/docker-kerberos-get-keytab
```
8 changes: 6 additions & 2 deletions config.ini
Original file line number Diff line number Diff line change
@@ -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
# 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
31 changes: 28 additions & 3 deletions docs/Responses.md
Original file line number Diff line number Diff line change
@@ -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": {
Expand All @@ -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": {
Expand All @@ -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": [
Expand Down
6 changes: 5 additions & 1 deletion runbooksolutions/agent/Agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 12 additions & 3 deletions runbooksolutions/auth/Auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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',
Expand Down

0 comments on commit c48f21d

Please sign in to comment.