Rebuilding a Faction: Part 1

Whats next for Faction as it becomes the greatest C2 Framework on the face of the earth.

In designing Faction, I had a series of goals in mind. I wanted it:

  • to be easy to extend and develop for

  • to be easy to understand how things work

  • to log everything, allowing operators to recreate the steps of their engagement

The design was a good effort, but as I worked with it more it became clear that I hadn't hit those first two goals. Developing for Faction was kind of a pain and several times issues made their way into releases that were caused by the complex development process. I'm still very proud of the design, but I know I can do better.

This is the first part of a (hopefully short) series of articles documenting the changes coming to Faction. The goal of this series is to document design decisions and features as they make their way on to GitHub. Ideas are cheap, so I want to make sure I have the code to back them up. First up:

Making Microservices more Microservice-y

Faction was designed to take the standard features of a C2 and break them out into separate services. This basically amounted to:

  • Transport services are what agents connect to (taking the role of redirects in a more typical C2 deployment)

  • Build services build modules and payloads

  • An API service provides external access to Faction

  • RabbitMQ is how services talk to each other

  • PostgreSQL is the data store for Faction

  • Core handles anything not covered in the above.

The design largely failed to offer the flexibility in development and ease of understanding that I was after. Each service is tightly coupled and the multiple pathways of communication (everything internal talks to RabbitMQ and Postgres) made it difficult to add new features or understand what was responsible for what. Even the API was more complicated than I would have liked, the design was solid but providing a separate REST and SocketIO API lead to differences in what each API could do and uh... I kind of never got around to documenting the SocketIO part. Whoops.

There's a lot of work I have planned to address this, but the first step was changing how the API works. Actually, this whole redesign came about when I learned about GraphQL

Why GraphQL is the greatest API ever

Whereas REST APIs require you to hit a unique endpoint for each object type, GraphQL presents a JSON like query language, allowing you to ask the API for exactly what you want. It's very cool. For example, if you wanted to get a list of properties for each agent, as well as details on its agent type and transports (which are two seperate tables) you'd write something like this:

query allAgents {
agents {
id
hostname
visible
agent_type {
name
id
}
transport {
id
name
}
}
}

Which would return a JSON blob something like the following:

agents [
{
id: 1
hostname: foo
visible: True
agent_type: {
id: 1
name: Marauder
}
transport: {
id: 1
name: HTTP
}
},
{
id: 2
hostname: bar
visible: True
agent_type: {
id: 1
name: Marauder
}
transport: {
id: 1
name: HTTP
}
}
]

The ability to completely customize the data you're looking for is an incredible feature as an API consumer. This feature becomes even awesomer when you factor in subscriptions. Any query you submit to GraphQL can be turned into a subscription. This opens a WebSocket connection to the API and as the data for your query changes, those updates are pushed down to your application, providing a real time feed of the information you need. For example, if you created a subscription out of the query above, as new agents connected you would get their details as well. Its an incredibly powerful feature and it means that we can rely on GraphQL for both the request based *and* real-time API for Faction.

In the Faction redesign, Hasura provides the GraphQL API. Hasura takes a Postgres database and turns it into a GraphQL API. This provides a series of benefits to the project:

  • Hasura provides a graphical query editor, which makes it really easy to design new queries.

  • Hasura provides granular authorization

  • Hasura is a large and well supported project, which means its probably a lot better than anything I would write.

  • Since it's built on top of PostgreSQL, Postgres can act as a single source of truth for Faction data and its format.

The GraphQL API also serves as the datastore API for Faction and will support both external access as well as the internal microservices. This means that I can drop RabbitMQ, and restrict access to Postgres to just Hasura, greatly simplifying how communication works in Faction.

Development and Deployment

With the focus on a better microservice design, the new Faction is being created as a Kubernetes application. I haven't been this excited about a technology in a long time. Kubernetes solves several problems in the original Faction design and provides a slew of new opportunities. It's designed for microservice applications, so leveraging it was an obvious choice.

Here's a brief overview, but cut me some slack on the technical details (or submit a PR), Kubernetes is a massively complex system and I have only the bare minimum experience with it.

Why Kubernetes is the greatest thing ever

Kubernetes serves as a kind of operating system for the cloud, allowing you to deploy complex applications using a standardized API. Kubernetes (k8s) provides a series of objects that make up a modern application, you define your application using these objects, and the underlying kubernetes installation helps translate these objects to their cloud (or bare-metal) specific counterparts. Objects cover things like load balancers, permanent storage, containers, configurations, and secrets.

You define your application in a series of YAML files which are then submitted to k8s. Kubernetes then handles applying these changes to the cluster. This allows you to easily deploy the same application to your localhost or a cloud provider with minimal changes. This also greatly helps the development process cause now what runs on your box is exactly (probably) what's going to run in production.

There are plenty of options for running a local k8s instance on your machine. I've been using microk8s and it's been absurdly easy to work with. For development, I've been using Skaffold which watches your entire application (both the code and k8s config) and re-deploys everything as changes happen. Live code reload is a familiar concept to anyone that's worked with a nodejs based app or Flask or something, the cool thing about this is it's doing that live reload for both applications and infrastructure.

In Faction's case, Kubernetes handles the ingress HTTP traffic including setting up SSL and routing requests to their appropriate service. It also centralizes configuration across all the services, allowing end-users to easily update the settings for a service (for example rotating a shared secret or changing a config value). As part of the release of new Faction, I'll provide some basic documentation on how to use Kubernetes so it won't be a completely foreign experience.

The goal will be to provide easy to follow instructions on how to deploy Faction to bare metal, as well as major cloud providers.

What's next

I have the redesign largely worked out on paper and there's a couple more things that I'm really excited about. In the next post I should be ready to talk a bit about how services work in this new setup, some of the services that make up new Faction, and how agent design has been effected, all of which has (hopefully) been greatly simplified from the current design.

You can follow the current state of the redesign in the main Faction repo on its k8s branch.

‌Jared