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

Uploaded CPython 3.13musllinux: musl 1.2+ ARM64

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

Uploaded CPython 3.12musllinux: musl 1.2+ ARM64

fexec-0.1.45-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.45-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.45-cp313-cp313-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for fexec-0.1.45-cp313-cp313-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 4786efd557273d01ed17fb105829d0418ed107baca6445c75d642a34cfb31db9
MD5 246dc02267774f9a1945cd4af8204e50
BLAKE2b-256 8c89a6f39b16b3cadcf6a60cd3e296250aebbfd301543183e709ed2c9346b306

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.45-cp313-cp313-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 faea027ad6741c7b7d475be23d416f1ad00c6583c1530af0614a3d080622ef8a
MD5 7fd624ef50cbc8f208fe028f803102d3
BLAKE2b-256 3110f061d82274e230d4f1827e5d22c54d6d809e3662c0353d3593c68ea93abf

See more details on using hashes here.

File details

Details for the file fexec-0.1.45-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.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a6fdab29dec80c7b618fce658c0c4b0ed0bcc52088a43ce20695d116a963160c
MD5 94b90cbb38b1cd25848e007ded4cfa43
BLAKE2b-256 ad409d4c56f61139333ec53b4548b5e17791e850a8716af52c24cd13162ff6fc

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 f736cf498d7a63e88ba59f11568928668fc7615cead536c6eca23b90c4625016
MD5 ecb107361b26340a8014e4ec6a7e6ea0
BLAKE2b-256 dc702b3f6425e230ac5d2e615d852538aac768bbeb9e476e1f879d2c63b1580d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.45-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 0c291288de8a44819be58ded2ef692a0fdfb4988021281f7f599011b53637848
MD5 4499bcf0099c67f84a7378df7730b084
BLAKE2b-256 2c0b8bc86fd9234f333964af6a58595a98e12dca272582488acdc7e622123a62

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.45-cp312-cp312-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 0159bfc1dc2a525d678d21f402f5457660f1179f18d6410406b260498e6c1ed4
MD5 1e3274c1b733db5e206e5dc430f85013
BLAKE2b-256 9c829f32a7596f27912dc35ea253685d4f789f39e030d0e5b3a7ed1631ccf6ec

See more details on using hashes here.

File details

Details for the file fexec-0.1.45-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.45-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c364977b3b57cc76bac220473ab39388e57357790aba6f82c5912d33b779a10f
MD5 1e5afe12043307afcd9e9a5e2b8d3723
BLAKE2b-256 fe1facad07bd36609f8cc924580b01dd2d11b7893e050cd072c654f85c1b9e25

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for fexec-0.1.45-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 14a95f057c247d13791ded662a6948ce0cda307ddeb6f515509478019be961f8
MD5 facd27a51b7681370cd59504503425f7
BLAKE2b-256 b7dfd42e9201795d5fe5038f80cd9eb40f90c68ec19feda997cdb054d5f9ccd4

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