Skip to content

Introduction and Concepts

maxlandon edited this page Jul 18, 2026 · 1 revision

Introduction & Concepts · [User+Dev]

This page gives you the mental model behind reeflective/team, the vocabulary used everywhere in the code and docs, and the single most important design decision: the library is an authentication authority, not an authorization one.


The problem it solves

You wrote a Go tool. Now your team wants to use one shared instance of it — together, securely, each from their own machine. Suddenly you need user authentication, TLS certificates, a database, one or more network listeners, a way to hand out and import connection configurations, and a CLI to manage all of it. None of that is your tool's actual job.

reeflective/team is exactly that infrastructure, done for you.

Programs that fit the model:

  • A C2 framework whose operators all drive one shared server (the library was extracted from one).
  • A password cracker whose lightweight clients offload jobs to a GPU/compute host.
  • Any tool that should sometimes be just a local command and sometimes a server for the team — same binary, same code, decided at runtime.

Mental model: embed once, many operators

Two ideas stay true throughout:

  1. A program is a client of its peers as much as a server to them. The same teamclient code talks to an in-process server or a remote one; the call site does not change. Network location is irrelevant to the model — "remote" just means a distinct process.
  2. Humans use software, not the inverse. The library serves both the developers embedding it (a small Go API) and the users operating it (an embeddable CLI tree with shell completion), each through an interface fit for them.
flowchart LR
    U1["operator's tool<br/>(teamclient)"] -- "remote: mTLS + token" --> TR
    U2["operator's tool<br/>(teamclient)"] -- "remote: mTLS + token" --> TR
    SELF["same program<br/>(in-memory teamclient)"] -. "no network" .-> TR

    subgraph S["your program as a teamserver"]
        direction TB
        TR["transport / RPC<br/>(gRPC example, or your own)"]
        SEC["authentication & PKI<br/>certs · users · tokens"]
        DB[("database<br/>sqlite by default")]
        LOG["loggers · audit"]
        TR --> SEC
        SEC --> DB
        SEC --> LOG
    end
Loading

The core (the client and server packages) owns users, certificates, the database and logging. It does not own the transport: you plug a transport/RPC backend into the server (a Handler) and a matching dialer into the client (a Dialer). A ready-made gRPC backend lives in example/.


The teamserver is a component, not the application

A program built only around server.New — with no logic of its own — is a valid teamserver, but it just manages users, listeners and configs. It does nothing your tool actually does, and is useless on its own. That standalone shape is handy for demos, tests, or a dedicated admin binary.

In a real tool you graft the generated command tree onto your application's own root command, where it becomes a teamserver subcommand alongside everything else your program does. That is exactly why users type cracker teamserver daemon, not teamserver daemon.

See Getting Started for the grafting code.


Authentication only — authorization is yours

This is the defining constraint of the current library direction. Read it once and it explains most of the API:

  • The teamserver proves who is calling. It holds each user's cryptographic material (a per-user token + client certificate) and answers exactly one identity question through server.Authenticate(token) (*team.User, error).
  • The returned team.User carries a name and registry metadata — no permissions, no roles.
  • Applications own authorization entirely. You resolve the returned user.Name against your own model (roles, an operator record, ACLs) and inject your own identity object into the request context from your own transport middleware.
// In your transport middleware (see the gRPC example):
user, err := teamserver.Authenticate(rawToken) // core: "who is this?"
if err != nil {
    return errUnauthenticated
}
// YOUR job from here: resolve user.Name against your own authz model,
// build your own identity type, put it on the context under your own key.
ctx = context.WithValue(ctx, myIdentityKey, myApp.LookupOperator(user.Name))

Authenticate is the single seam through which an embedding application learns "who is calling." Everything about what they may do lives in your code.


Terminology

Used throughout the code and docs:

Term Meaning
teamclient The client-side toolset the library provides (team/client.Client), or the software embedding it.
teamserver The server-side toolset (team/server.Server), or the software embedding it.
team tool(s) Any program using either or both components.
Handler A transport/RPC stack registered with a teamserver (keyed by Name(), e.g. "gRPC"). See Writing a Transport.
Listener A running bind job created by serving a Handler on a host:port. Controlled via Listeners() / ListenerClose().
Dialer The client-side counterpart of a Handler: initiates and owns the connection to a teamserver.
user A registered identity for which the teamserver holds token + client certificate.
config A JSON teamclient connection file (*.teamclient.cfg) containing host/port, user, token and TLS material handed to an operator.

Core types

Two small exported types in the root team package are the shared vocabulary between clients and servers:

// team.User — a registered identity. NOTE: no permissions/roles by design.
type User struct {
    Name     string    // Name of the user
    Online   bool      // Are one or more of the user's clients connected
    LastSeen time.Time // Last RPC activity
    Clients  int       // Number of connected clients
}

// team.Version — build/version info for a binary (client and server can differ).
type Version struct {
    Major, Minor, Patch int32
    Commit              string
    Dirty               bool
    CompiledAt          int64
    OS, Arch            string
}

// team.Client — the minimum a (possibly remote) teamserver must answer.
type Client interface {
    Users() ([]User, error)
    VersionServer() (Version, error)
}

What you get out of the box

  • Works immediately — pure-Go sqlite database, file + console logging, a full mTLS PKI, all configured for you.
  • Local and remote, same code — in-process (no network) or against a remote server; the calling code is identical.
  • Secure by default — mutual-TLS transport, certificate-based user authentication, per-user tokens, zero-trust posture between clients and servers.
  • Batteries, but swappable — replace the transport/RPC layer, the database, the loggers or the filesystem when you outgrow the defaults, keeping the rest.
  • Two audiences, two interfaces — a small Go API and an embeddable CLI tree.
  • Automation-friendly — non-blocking API, systemd unit generation, persistent listeners, importable client configs.

Behavioral notes

  • All errors returned by the API are logged before being returned.
  • Filesystem interactions are deferred until they actually need to happen.
  • Critical errors are returned rather than log.Fatal/panicexcept the certificate infrastructure, which must succeed for security reasons.
  • Except server.ServeDaemon (behind teamserver daemon), all API functions and interface methods are non-blocking.
  • The loggers handed out by the cores are never nil.

Next: Getting Started / Embedding →

Clone this wiki locally