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.46-cp314-cp314-musllinux_1_2_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.14musllinux: musl 1.2+ x86-64

fexec-0.1.46-cp314-cp314-musllinux_1_2_aarch64.whl (5.0 MB view details)

Uploaded CPython 3.14musllinux: musl 1.2+ ARM64

fexec-0.1.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (5.3 MB view details)

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

fexec-0.1.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl (5.1 MB view details)

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

fexec-0.1.46-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.46-cp313-cp313-musllinux_1_2_aarch64.whl (5.0 MB view details)

Uploaded CPython 3.13musllinux: musl 1.2+ ARM64

fexec-0.1.46-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.46-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.46-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.46-cp312-cp312-musllinux_1_2_aarch64.whl (5.0 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ ARM64

fexec-0.1.46-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.46-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.46-cp314-cp314-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for fexec-0.1.46-cp314-cp314-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 e0a57bcb34d01592acd04bcee3204a75fe63b9ad9b71283e875fe19dbbc215cf
MD5 4d8553f9a7db08ce8dd12c8a7cfe1437
BLAKE2b-256 91d468282c72986c9ceb5ebe4fea978d02bb3dc0907e6d0da4ecb85f1270f6f2

See more details on using hashes here.

File details

Details for the file fexec-0.1.46-cp314-cp314-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for fexec-0.1.46-cp314-cp314-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 d789c9f4d27665e90b5b5185b9637d36ebc698a455b2f1d68792f5f2b72499f6
MD5 2b68afd94c98ffe67dc786ae5eaa3aef
BLAKE2b-256 5d4bac8a868b330253372e2afe084db3df168168c23dcc65add976512ef01e03

See more details on using hashes here.

File details

Details for the file fexec-0.1.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fexec-0.1.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 61e10739d3892dce17c53ffbebe37f016001c57593562ec5c8f6a033fb617636
MD5 848dca209156366e3b2f4e42147dcfc8
BLAKE2b-256 ed5d65a7651cd9797b737f1dcf2e982f582e995b8307eb2e74aa8643d31a0001

See more details on using hashes here.

File details

Details for the file fexec-0.1.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for fexec-0.1.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 6cb13d94a7ce8c867cb031c83f3a01596850cbad6dade0e21dd7323ff3ee76de
MD5 a8fcc566ded9c44ff8a87dd3036441ce
BLAKE2b-256 22fce3806432575ba0285528477681bf300402ccb2d29f2fee5398f2452a8898

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.46-cp313-cp313-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 443deb4a5434cda961c8bc249219ad1a2796f3bc51b09c428095286e98181d74
MD5 0d35082026cd9452649ad6e74bcd5b4a
BLAKE2b-256 b7e20768d6a5f8c46dbc10e4da10472e82e5070d9c9f3189b623aded53d099da

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.46-cp313-cp313-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 9066fb453cec06d6c7131fc265e03f71f975508f4fb6c76e811f504de9c617e9
MD5 1a7ab28b2c27f8f75885bb876128b08d
BLAKE2b-256 2988c6a79a904fc8f4a5de70b4c6a246db0089042f53e9cd7e337e66e29dd9fd

See more details on using hashes here.

File details

Details for the file fexec-0.1.46-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.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f33b62ab3ce065ca0b0787ac7ca091eec763b9a79a91f21210fd5d989981aa3a
MD5 1de2aea329cb7c0fc79fb5c98d8b28e6
BLAKE2b-256 beecbc3be1d2da59d21b76765cd7e69e54575ba9a3e3305098cd60e215c3e343

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 6d4c9339e70b2a443c3544c3b4463f5cb45ed3697b9b0d4c5d3e3190ee6e3d87
MD5 56640a4bd2818933e0ab570fe3673ff8
BLAKE2b-256 fcd31c9d2e8770442c3cede74f693d9120426262cd760484ef36acb2591e4b43

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.46-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 7394b1ab57ca6de761c0658eeb04e35a92e488443ac4d621dbee413daedbd122
MD5 535c1af881e79be1a4cffea70c528086
BLAKE2b-256 385089a2213f2fb90a0f573cdb82b6a6d2eaaac866a1122a94fa2fee6135560c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.46-cp312-cp312-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 6bf502aca536cc1142a85c730b10bf4102c54a3c5f2a8412475026cc8025b83e
MD5 8054134b4f2fd6240f4520dfac4c486f
BLAKE2b-256 7bb15f5ca7b8b3da2089121383fe8d42d90600c80d611beb3dde553583811336

See more details on using hashes here.

File details

Details for the file fexec-0.1.46-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.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 32cf37c48fef0d8a9f049ac962dbe0e13cda5cc2c3a71eb606610153a591a958
MD5 9d4b9b0e68a3a02f985bf44d16d96102
BLAKE2b-256 1036650748a965ee817ff80f23b9bcd84b848411a3afe5b8f77c81041b2a7cda

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 e91c1e5a617c10a2b8254bf4c71f4cb90f49f43df11139a44628e11cf11f0b2f
MD5 518fdb66a29d579c16c1d5e112f59d94
BLAKE2b-256 7b00be1853c19206114f9d7122f2b7565d6851992fc8242b4141d2a46abd440b

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