-
Notifications
You must be signed in to change notification settings - Fork 2
Introduction and Concepts
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.
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.
Two ideas stay true throughout:
- 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.
- 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
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/.
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.
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.Usercarries a name and registry metadata — no permissions, no roles. -
Applications own authorization entirely. You resolve the returned
user.Nameagainst 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.
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. |
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)
}- 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,
systemdunit generation, persistent listeners, importable client configs.
- 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/panic— except the certificate infrastructure, which must succeed for security reasons. - Except
server.ServeDaemon(behindteamserver daemon), all API functions and interface methods are non-blocking. - The loggers handed out by the cores are never nil.