Skip to content

Security: ossko/colonies

Security

docs/Security.md

Security model and hardening

ColonyOS uses zero-trust, signature-based authentication: every authenticated RPC is signed with the caller's secp256k1 private key and the server recovers the caller's identity from the signature. The only exceptions are the version and server-info messages (versionmsg, getserverinfomsg), which are answered before signature verification. There are no cookies or sessions, so CSRF is not a concern; authorization is checked per handler against the recovered identity.

This document covers the transport- and protocol-level hardening added in v2.0 and the settings that control it. All settings live in the Security config section and can be set with COLONIES_SECURITY_* environment variables (or the config file). Defaults are chosen to be safe without breaking existing executors and SDKs.

Identities and what they may do

Three kinds of identity exist, and the handlers check the recovered identity against one of three predicates:

Predicate Satisfied by Guards
Server owner The server private key Adding and removing colonies, server statistics
Colony owner The colony private key Adding, approving, rejecting and removing executors and users, adding blueprint definitions, removing all processes of a colony
Membership Any approved executor or any user of that colony Everything else: assigning and closing processes, files, snapshots, logs, function registration, blueprint reconciles, and reading colony state

Membership is colony-scoped. An executor or user of one colony cannot act on another colony, and an executor cannot change its own executor type (the type used for process assignment is read from the database record, never from the request), so it cannot take over another executor's queue.

Executor submit permission

Membership on its own is broad: it lets a caller inject new work into the colony, which is then executed by other executors. Since executors are designed to run anywhere on the Internet, often on hardware that is less trusted than the server, a leaked executor key would otherwise be enough to run arbitrary workloads anywhere in the colony.

Executors therefore carry a cansubmit flag, controlled by the colony owner, which is required in addition to membership by every endpoint that injects work:

  • submitfuncspecmsg (submit a function spec)
  • submitworkflowspecmsg and addchildmsg (submit or extend a workflow)
  • addcronmsg and runcronmsg (add or trigger a cron)
  • addgeneratormsg and packgeneratormsg (add or drive a generator)

Users are unaffected: a colony user may always submit.

The flag defaults to true, so upgrading changes no behaviour. Executors registered before the flag existed, and clients that do not send the field, keep their previous ability to submit. Denying it is an explicit opt-in:

# an executor that may only execute, never submit
colonies executor add --name my-worker --type my-type --executorid <id> \
    --nosubmit --approve

# same for the key-generating variant
colonies executor create --name my-worker --type my-type \
    --keypath worker.prv --idpath worker.id --nosubmit --approve

The current value is shown by colonies executor get --name <name> as CanSubmit, and appears as cansubmit in the JSON representation of an executor. A spec file passed with --spec may set cansubmit directly; --nosubmit overrides it.

The permission is set when the executor is registered. To change it for an existing executor, remove and re-add it:

colonies executor remove --name my-worker
colonies executor add --name my-worker --type my-type --executorid <id> --nosubmit --approve

Re-adding reuses the existing record, so the executor keeps its identity and history. Note that this is a permission change only; if the executor's key is believed to be compromised, rotate the key rather than only clearing the flag.

Which executors should keep the permission is a per-deployment decision. A meta-orchestrating executor that decomposes work into sub-workflows needs it; a leaf executor that only pulls and runs processes does not, and is the case where --nosubmit reduces the blast radius of a compromised key the most.

HTTP transport

Setting Env Default Notes
Read-header timeout COLONIES_SECURITY_READ_HEADER_TIMEOUT 20 s Slowloris defense. Does not affect the response side, so the executor long-poll assign is unaffected. 0 disables.
Idle timeout COLONIES_SECURITY_IDLE_TIMEOUT 120 s Keep-alive idle bound. 0 disables.
Max body bytes COLONIES_SECURITY_MAX_BODY_BYTES 104857600 (100 MiB) Caps request bodies so an unauthenticated caller cannot exhaust memory. Also caps a single websocket message. 0 disables.
CORS allowlist COLONIES_SECURITY_CORS_ALLOW_ORIGINS empty Comma-separated browser origin allowlist. Empty installs no CORS middleware, so cross-origin browser requests get no allow header and are blocked by the browser; non-browser clients (executors, SDKs, CLI) send no Origin header and are always allowed. A non-* allowlist permits only GET/POST methods and the Origin, Content-Type, Accept headers. * restores allow-all.
WebSocket origin allowlist COLONIES_SECURITY_WS_ALLOW_ORIGINS empty Same semantics for /pubsub. Empty allows no-Origin (non-browser) and same-origin requests only. * allows any.
Rate limit COLONIES_SECURITY_RATE_LIMIT 0 (off) Per-client-IP requests/second on all routes; only /health is exempt. The assign long-poll and /pubsub subscribe count against the limit, so size it for the number of executors sharing a source IP.
Rate-limit burst COLONIES_SECURITY_RATE_LIMIT_BURST 0 (= rate) Allowed burst above the rate. 0 or negative falls back to the rate value.

The server always runs gin in release mode. When TLS is configured it is served with a minimum version of TLS 1.2.

Deliberately, no ReadTimeout/WriteTimeout is set on the HTTP server: the executor long-poll assign holds the response open, and /pubsub websockets are long-lived, so a blanket read/write deadline would break legitimate connections.

RPC replay protection

Signed RPC messages carry an optional nonce and timestamp. When the nonce is non-empty, the signed bytes are payload + "|" + timestamp + "|" + nonce, binding them against tampering; when the nonce is empty, only the payload is signed (the legacy scheme). The server rejects a message whose timestamp is outside the allowed skew window or whose nonce has already been seen.

Setting Env Default Notes
Mode COLONIES_SECURITY_REPLAY_PROTECTION advisory off (aliases disabled, none): ignore replay fields. advisory: check messages that carry replay fields, still accept legacy messages that omit them. enforce (aliases enforced, strict): additionally reject messages that lack replay fields. Any unrecognized value silently falls back to advisory, so a typo will not raise an error.
Window COLONIES_SECURITY_REPLAY_WINDOW 300 s Allowed timestamp skew. Nonces are retained for twice the window, since a message timestamped up to one window in the future stays valid until now + 2x the window.

Rollout is staged: v2.0 defaults to advisory so v2.0 clients are protected immediately while pre-v2.0 clients keep working. A later release is expected to default to enforce. Old clients that sign the payload only remain valid in off/advisory modes.

Cluster (multi-node only)

Single-node deployments run no relay or etcd. Multi-node clusters do, and those ports must be reachable only from trusted peers.

  • COLONIES_CLUSTER_SECRET authenticates the cluster relay: peers HMAC-sign each broadcast (sent in the X-Colonies-Relay-Auth header) and the receiver rejects unsigned or mismatched posts. Set the same value on every node. When unset, the relay logs a warning at startup.
  • The embedded etcd peer/client ports have no TLS or authentication. Restrict them to a private/firewalled network. The server logs a warning when etcd binds to all interfaces in a multi-node configuration. (Full etcd mTLS is planned; see the modernization backlog.)

S3 credentials

S3 credentials are read from server/executor configuration (AWS_S3_*), never from per-file database records. The server does not persist or return S3 access keys, secret keys, or encryption keys; a migration blanks any that older versions stored. See modernization/DB-MIGRATION-NOTES.md.

There aren't any published security advisories