Skip to main content

Secure Execution Gateway - API-first service for executing pre-defined system commands

Project description

FExec - Secure Execution Gateway

FExec is an HTTP API service that executes pre-defined system commands or Docker container tasks on demand. It acts as a security boundary between untrusted callers and the host execution environment: only explicitly configured command aliases can be triggered, and every parameter is validated before use.

Objectives

Containers that provide functionality used only occasionally still consume memory and CPU while they sit idle, waiting to be needed. If usage follows a regular schedule, a cron job can start the container when needed and stop it afterwards. If usage is purely interactive, a person can start it by hand. But when the functionality is needed on demand — triggered by an event such as a step in an automated workflow — neither option fits: nothing is watching for the event, so the container either runs continuously to be ready (wasting resources) or isn't there when the event happens.

The obvious workaround, for local automation, is to give the calling process access to the Docker socket so it can start containers itself. That solves the on-demand problem, but at a cost: anything with socket access can run arbitrary containers with no restriction on image, command, or resources — an unauthenticated, unaudited path to full control of the Docker daemon.

FExec exists to close that gap. It replaces direct Docker socket access with a narrow, authenticated HTTP API: callers can only trigger commands or containers an operator has explicitly pre-defined as aliases, with every parameter validated before use. Functionality that's needed only occasionally can be started exactly when it's needed — without leaving it running all the time, and without handing callers unrestricted control of the host.

Background

FExec grew out of a concrete need: running browser-based web automation (via Playwright) as part of a larger workflow. Playwright containers are memory-heavy, and running them permanently — especially alongside other, similar occasional-use workloads competing for the same memory — wasn't a good trade-off.

The automation itself was orchestrated by an internal n8n instance, but the intent was to expose the ability to trigger it more broadly, including to callers outside the local network, while tightly restricting which containers could run and what they were allowed to do. That requirement put potentially dangerous operations — starting arbitrary containers or processes — behind three layers of protection: authentication of every call to FExec (calls to the Docker socket itself are never authenticated), a strict, operator-defined whitelist of what can be run at all (no arbitrary commands, ever), and granular authorization that determines, per caller, which of those pre-defined aliases they're actually allowed to invoke.

Execution Modes and Types

Every action FExec's API can perform is defined as an alias — a named, operator-authored configuration that combines what to run, how input and output are mapped, how it's logged, and other execution details. Each alias is configured along two independent dimensions: type (what runs) and mode (how its lifecycle and invocation are handled).

Each alias is configured as one of two execution types:

  • Container — FExec starts a Docker container, runs it to completion, and returns the result. Input and output are exchanged with the containerized process through whichever combination of environment variables, extra command-line arguments, streams, and files the alias maps to the API's parameters.
  • Process — FExec starts a normal host process instead of a container, using the same range of input/output mapping options (environment variables, command-line arguments, streams, files).

Both types share the same authentication, authorization, and parameter validation — the only difference is what actually runs: a container or a host process.

Every alias also declares a mode, mandatory, which currently must be inline — the only mode supported today. In Inline mode (for both Container and Process types), the API call blocks until the container/process runs to completion, and its output/exit code is returned inline with the response.

How It Works

Clients call FExec over HTTP, authenticating with a JWT or API key. FExec looks up the requested alias, validates all parameters against the alias's regex rules, executes the command (on the host or in a Docker container), and returns the result. No arbitrary commands can be run — only aliases defined by the operator.

Installation

pip install .

Or build a wheel first:

python -m build
pip install dist/fexec-*.whl

Quick Start

  1. Create /etc/fexec/fexec.conf (or use defaults — the file is optional; a warning is printed if it is absent):
ListenPort = 8080
LogLevel   = info
AppLog     = /var/log/fexec/app.log info json
AuditLog   = /var/log/fexec/audit.log
  1. Add at least one authentication file to /etc/fexec/auth.d/ and at least one alias to /etc/fexec/alias.d/. See CONFIGURATION.md for format details.

  2. Start the service:

fexecd
# or override specific settings via CLI flags:
fexecd --config /path/to/fexec.conf --listen-port 9000
# or via environment variables:
FEXEC_LISTEN_PORT=9000 FEXEC_LOG_LEVEL=info fexecd
# run as a background daemon (detach from terminal):
fexecd --daemon
# print usage and all accepted flags, then exit:
fexecd --help
# print version string and exit:
fexecd --version

API

Authentication

Every request must include one of:

Header Value
Authorization Bearer <JWT>
X-API-Key <key>

Endpoint

POST /v1/{alias_name}

Request Body

Parameters can be sent in any of the following formats:

JSON (Content-Type: application/json):

{ "param1": "value1", "param2": "value2" }

Form-encoded (Content-Type: application/x-www-form-urlencoded):

param1=value1&param2=value2

Multipart form (Content-Type: multipart/form-data):

-F param1=value1 -F param2=value2

A request with no body (absent or zero Content-Length) is always treated as having no parameters, regardless of the Content-Type header. A request with a non-zero body and an unrecognised Content-Type is rejected with 400.

Response Codes

Code Meaning
200 Success — body contains the alias output. Also returned when the command exited non-zero but the alias's exitCodeMap explicitly maps that exit code to 200
400 Bad request — missing/invalid/extra parameters
401 Missing or invalid credentials
403 Credentials valid but alias not permitted for this user
404 Alias does not exist
408 Execution timed out and the process/container was killed by FExec
422 Command completed but exited with a non-zero code (default; overridable per alias via exitCodeMap — see Exit Code Status Mapping)
500 Command executed but failed internally (infrastructure-level failure, e.g. Docker unreachable)
503 Service is shutting down

200 from FExec means both FExec and the invoked process/container succeeded — not merely that FExec was able to run it — except when exitCodeMap has explicitly reassigned that exit code's status.

Example

curl -X POST https://localhost:443/v1/generate-report \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <jwt>" \
  -d '{"type": "pdf", "date": "2024-01-15"}'

Command Line Reference

Run fexecd --help (or fexecd -h) to see the full usage summary and exit without starting the service.

Flag Metavar Config key Environment variable
--help, -h
--version
--config PATH
--daemon
--pid-file PATH PidFile FEXEC_PID_FILE
--auth-dir DIR AuthDir FEXEC_AUTH_DIR
--alias-dir DIR AliasDir FEXEC_ALIAS_DIR
--shared-dir DIR SharedDir FEXEC_SHARED_DIR
--external-dir DIR ExternalDir FEXEC_EXTERNAL_DIR
--listen-address ADDR ListenAddress FEXEC_LISTEN_ADDRESS
--listen-port PORT ListenPort FEXEC_LISTEN_PORT
--ssl-enabled true|false|1|0 SslEnabled FEXEC_SSL_ENABLED
--ssl-cert PATH SslCert FEXEC_SSL_CERT
--ssl-key PATH SslKey FEXEC_SSL_KEY
--log-level LEVEL LogLevel FEXEC_LOG_LEVEL
--log-dir DIR LogDir FEXEC_LOG_DIR
--audit-log PATH AuditLog FEXEC_AUDIT_LOG
--app-log SPEC AppLog FEXEC_APP_LOG
--auth-reload SECONDS AuthReload FEXEC_AUTH_RELOAD
--alias-reload SECONDS AliasReload FEXEC_ALIAS_RELOAD
--container 0|1 Container FEXEC_CONTAINER
--max-timeout SECONDS MaxTimeout FEXEC_MAX_TIMEOUT
--refresh-image always|on-start|no RefreshImage FEXEC_REFRESH_IMAGE
--prefetch-image on-start|on-config-change|no PrefetchImage FEXEC_PREFETCH_IMAGE

Precedence (highest first): CLI flag → environment variable → fexec.conf → built-in default.

Configuration

FExec is configured through four layers (highest precedence first):

  1. CLI flags — e.g. --listen-port 9000
  2. Environment variablesFEXEC_<PARAM> e.g. FEXEC_LISTEN_PORT=9000
  3. /etc/fexec/fexec.conf — main service settings (port, SSL, log file, directories, reload intervals)
  4. /etc/fexec/auth.d/*.yaml — users with API keys and permitted aliases; JWT public keys
  5. /etc/fexec/alias.d/*.yaml — command aliases (what to run, parameters, output format)

All configuration is loaded at startup and validated. Any error causes FExec to refuse to start and print all errors to the console and log. FExec also refuses to start if no aliases or no authentication (users or JWT keys) are configured, or if the SSL certificate is expired or mismatched with its key. At runtime, auth and alias configuration is reloaded automatically when files change (configurable interval) and on SIGHUP. The main fexec.conf requires a restart to re-apply.

See CONFIGURATION.md for a complete reference including all parameters, formats, and examples.

Process Management

Signal Effect
SIGHUP Reload auth and alias configuration (non-disruptive)
SIGTERM Graceful shutdown — stops accepting new requests, waits for in-flight ones to finish
SIGINT (Ctrl-C) Same graceful shutdown as SIGTERM
SIGKILL Immediate termination

A PID file is written to /run/fexec.pid (configurable via PidFile).

Security Model

  • Whitelist execution: only pre-configured aliases run; no arbitrary commands
  • Per-parameter validation: every input validated against a regex defined in the alias
  • Container isolation: Docker aliases run with --network none, --read-only, --cap-drop ALL
  • No state leakage: per-execution temp directories are deleted after each run
  • Opaque errors: internal failures return 500 without stack traces; details go to the log
  • Audit log: every call is recorded with user identity, alias name, parameters, and exit status

Logs

FExec writes two independent log streams:

Stream Default path Content
AuditLog /var/log/fexec/audit.log One nginx-style line per request: timestamp, source IP, user, alias, duration, HTTP status, ApplicationCode, exit code, PID (process aliases) or container/image ID (container aliases), correlation ID
AppLog /var/log/fexec/app.log Application diagnostic messages (startup, errors, warnings). JSON format by default. Level-filtered — default level is warn (set LogLevel = info to see startup messages)

Both targets are configurable in fexec.conf and accept /dev/stdout or /dev/stderr as the path. A relative path for either is resolved against LogDir (default /var/log/fexec). When running interactively (stdout is a TTY) and neither log targets stdout/stderr, AppLog entries are also echoed to stdout in plain-text format at the configured LogLevel.

Every failed-request entry in AppLog includes the same correlation ID as the corresponding AuditLog line, allowing the two to be cross-referenced.

Volume Preserve

Container-type aliases can mark a volume with preserve: flatten or preserve: yes (in its YAML definition) to have that volume's content copied to LogDir after the container finishes, surviving the run directory's cleanup independent of the alias's output. See CONFIGURATION.md for the full semantics (applicability, naming, and collision handling).

Stream Preserve

Any alias (process or container) can set preserveStreams: true, or list individual stdout/stderr entries, to have that execution's stdout/stderr copied to LogDir after it finishes, independent of the alias's output. See CONFIGURATION.md for the full semantics.

Exit Code Status Mapping

By default a zero exit code returns HTTP 200 and any other exit code returns HTTP 422 — the response body is always built from the alias's output config either way. Any alias can override this via exitCodeMap, mapping specific exit codes or ranges to different HTTP statuses (including back to 200). See CONFIGURATION.md for the full semantics.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

fexec-0.1.39-cp313-cp313-musllinux_1_2_x86_64.whl (5.3 MB view details)

Uploaded CPython 3.13musllinux: musl 1.2+ x86-64

fexec-0.1.39-cp313-cp313-musllinux_1_2_aarch64.whl (5.0 MB view details)

Uploaded CPython 3.13musllinux: musl 1.2+ ARM64

fexec-0.1.39-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (5.3 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

fexec-0.1.39-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl (5.2 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ ARM64manylinux: glibc 2.28+ ARM64

fexec-0.1.39-cp312-cp312-musllinux_1_2_x86_64.whl (5.3 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

fexec-0.1.39-cp312-cp312-musllinux_1_2_aarch64.whl (5.0 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ ARM64

fexec-0.1.39-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (5.3 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

fexec-0.1.39-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl (5.2 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64manylinux: glibc 2.28+ ARM64

File details

Details for the file fexec-0.1.39-cp313-cp313-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for fexec-0.1.39-cp313-cp313-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 88cb8d502688e1b0442afd4229969591c101503c317b2c52a56aa64287ae29b4
MD5 0286092c6fb902e80cc69c6b8eed77b1
BLAKE2b-256 e83c0eb3a4c5066911d2504a070345c51f6055b3b8d4a7d9fdb35c54369e4a09

See more details on using hashes here.

File details

Details for the file fexec-0.1.39-cp313-cp313-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for fexec-0.1.39-cp313-cp313-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 da8077301b38940bad5439c9b0aec7449e178752aed0032b46ec7ab5e0c5ef49
MD5 2baee5a4cc2ba10c2a385874c9cb3bd8
BLAKE2b-256 c18eeec3c87e185587f1a38e9dd9715e45b7160e2b7c1979e2f0a5fdfbb8f8a5

See more details on using hashes here.

File details

Details for the file fexec-0.1.39-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fexec-0.1.39-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 3abb3917b4aef7bcd8ee3da7266fc29087734f4e518bc3c7f5796389431e6d7c
MD5 144db3964be8618c8a4d6c62163f1845
BLAKE2b-256 bda91fff8755a2959e6e3be1d60b691000f298145deb8fb5b7cb57c772faaf1f

See more details on using hashes here.

File details

Details for the file fexec-0.1.39-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for fexec-0.1.39-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 443165dc3f4b35b427f706926b9085ec92fc96a88971bda35e68f7f64045ca5a
MD5 25e5122d4cf462c68e28c10d1314f6c3
BLAKE2b-256 02737c0336d2fa37f899e88155e09d7187086683e27c135019dfd114ad9a5aa1

See more details on using hashes here.

File details

Details for the file fexec-0.1.39-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for fexec-0.1.39-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 65a68412320793047720daf0312becec9e21fcf9704f4b515536a8d236e89e82
MD5 f85f245d0ab8c62cd5d50cfb897d2599
BLAKE2b-256 f88edf59cde8a0e0b3e74edbf74dd0f11691ad890b3fc7f10811b8b961bf44d7

See more details on using hashes here.

File details

Details for the file fexec-0.1.39-cp312-cp312-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for fexec-0.1.39-cp312-cp312-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 6d16692688657c7c9dbb0b83b5e296034ed88e7472ec1cb483ed78c456126337
MD5 97c2ca930f3ce23a0c3288596b7f4853
BLAKE2b-256 5b9da9ee07133b23a7febf4e510019e5c5dad1787a9d0bd99a0497e2372e70bd

See more details on using hashes here.

File details

Details for the file fexec-0.1.39-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fexec-0.1.39-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 abcca855dcf1c96e26a00667087a91dde477df18bfb2cbd0ef3ef77d6b288bd0
MD5 56ddea0c1fb04e3556b1a1ab49495141
BLAKE2b-256 3d04e4d2ef0468ff7771ab75779a28b5af6dcde309a239f5550984cc6bd5c773

See more details on using hashes here.

File details

Details for the file fexec-0.1.39-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for fexec-0.1.39-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 5a0681017f894f8bc9e1ae4a8eb457fe01105014650d76d1b9e3a0c36b4d1499
MD5 e8fcf6a567f33820a603cd744c88eb82
BLAKE2b-256 e9fc73e0a42acd46358aa6e9291fa10d82610e23f90d7426b4dfba2a972845ff

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page