Aug 27 2024 |
Ghostwriter ❤ Tool Integration
Incorporating new components into existing systems is such a pain, this process has been labeled “Integration Hell”. To ease tool integration, Ghostwriter v3.0.0 shipped with a GraphQL API. This API allows outside entities to easily query and manipulate Ghostwriter’s data. In this blogpost, we’ll use our Operation Log Generator to demonstrate the capabilities of this API.
Ghostwriter & GraphQL
Ghostwriter’s GraphQL API is driven by the Hasura GraphQL Engine. This engine provides three broad GraphQL operations:
Queries – Query operations fetch data from Ghostwriter
query domainQueryID {
domain(where: {name: {eq: "mydomain.com"}}) {
id
}
}
Mutations – Mutation operations manipulate Ghostwriter data
mutation createSampleClient {
insert_client_one(object: {name: "SpecterPops", codename: "SAMPLE CLIENT", timezone: "America/Los_Angeles"}) {
id
}
}
Subscriptions – Subscription operations allow applications to receive data in real-time
subscription domainHealthMonitoring {
domain(where: {healthStatus: {healthStatus: {_neq: "Healthy"}}}) {
id
name
}
}
Schema Exploration
Ghostwriter’s GraphQL schema can be explored in two different ways:
- Hasura Console — The Hasura console allows direct interaction with the Ghostwriter’s PostgreSQL database. Changes made via the console could irreversibly break Ghostwriter, so this option is disabled by default
- Ghostwriter’s GraphQL SDL — Ghostwriter’s GraphQL schema is available on Github, and can be safely explored with Postman
The GraphQL operations defined by the schema provide powerful methods for Ghostwriter data management and the API allows external tools to easily leverage these operations. For example, our Python Operation Log Generator script leverages GraphQL mutation operations to create and fill a Ghostwriter operation log with entries we commonly see on our operations.
Sample Example
The Operation Log Generator uses a serries of Ghostwriter GraphQL mutations to create and populate an operation log. These mutations include:
- login – This mutation obtains a JSON Web Token (JWT) as part of the Ghostwriter API authentication process
- insert_client_one – Our script uses this operation to create a single sample Ghostwriter client: Specter Pops
- insert_project_one – The script uses this mutation to a single new project, SAMPLE PROJECT, to the newly-created sample client
- insert_oplog_one – We use this operation to generate a single operation log under the project: SAMPLE PROJECT
- insert_oplogEntry – This final mutation aggregates all operation log entries from the file: config/oplog.csv, before issuing a single GQL query to populate the newly-created sample operation log. If this file does not exist, the program creates an operation log with 5,000 procedurally-generated entries
These five API calls are all it takes to link our operation log generator to Ghostwriter! Let’s take a closer look at how our script actually implements these calls.
Authentication
The script first sets up token-based authentication with Ghostwriter. The steps to authenticate are outlined in Ghostwriter’s documentation, but at a high level, the program first use a username and password obtain a JWT with a login GraphQL API call.
def get_logon_token(credentials):
# Prepare our initial unauthenticated GraphQL client
transport = AIOHTTPTransport(credentials.url)
client = Client(transport=transport, fetch_schema_from_transport=True)
# Define our gql query
get_logon_token_query = gql(
"""
mutation login_mutation($password: String!, $username: String!) {
login(password: $password, username: $username) {
token expires
}
}
"""
)
get_logon_token_query_params = {
"password": credentials.password,
"username": credentials.username
}
# Login and get our token
login_result = client.execute(
get_logon_token_query,
variable_values=get_logon_token_query_params
)
return login_result["login"]["token"] # pylint: disable=unsubscriptable-object...
The script then creates a GQL Client object and uses the obtained JWT as the bearer token in an HTTP Authroization header.
...
from gql import Client, gql
...
# Use credential configs to get a Ghostwriter token
gw_auth_token = get_logon_token(credentials)
# Set up token-based authentication
headers = {"Authorization": f"Bearer {gw_auth_token}"}
transport = AIOHTTPTransport(credentials.url, headers=headers)
authenticated_client = Client(transport=transport, fetch_schema_from_transport=True)
...
This GQL Client object is then used to execute all other GQL operations.
API Usage
Once a GQL Client object has been created, executing operations is a simple three step process.
- Create a gql object by passing the constructor a string representation of the GQL operation
- Create a dict structure containing the operation’s variables
- Use the GQL Client object to execute the operation by passing in the gql and dict objects.
def create_sample_client(gql_client):
CLIENT_NAME = "SpecterPops"
CLIENT_CODENAME = "SAMPLE CLIENT"
TIMEZONE = "America/Los_Angeles"
# Define our GQL string
sample_client = gql(
"""
mutation create_sample_client(
$client_name: String!,
$code_name: String!,
$timezone: String!
) {
insert_client_one(
object: {
name: $client_name,
codename: $code_name,
timezone: $timezone
}
) {
id
}
}
"""
)
create_sample_client_param = {
"client_name": CLIENT_NAME,
"code_name": CLIENT_CODENAME,
"timezone": TIMEZONE
}
result = gql_client.execute(sample_client, variable_values=create_sample_client_param)
return result
Ghostwriter’s GraphQL API allowed us to quickly link our Operation Log Generator to Ghostwriter. Now, we can test procedurally-generated operation logs to our heart’s content.
Ghostwriter ❤ Tool Integration was originally published in Posts By SpecterOps Team Members on Medium, where people are continuing the conversation by highlighting and responding to this story.