Skip to content

Engineering apps

Connectors

19 min read

A connector is a pure-Python, source-only function you deploy into your own cloud account — AWS Lambda, a GCP Cloud Function, or an Azure Function — that an OpenEng app engine calls over HTTPS to reach a resource the engine can't touch directly: a private Kubernetes cluster, a cloud database, an object store, a managed shell. Your cloud credentials stay in your account and nothing passes through OpenEng. Results come back to your engine over HTTPS. The engine holds the connector's URL, API key, bearer token and write key, and sends them only to your gateway. The marketplace at connectors.openeng.app ships 252 ready packages — 80 capabilities across AWS, GCP and Azure plus 12 standalone connectors (Kubernetes, Kafka, two object stores, and eight databases) — every one of them stdlib-only source you can read before you run.

What a connector is

The OpenEng apps (Terminal, Kubernetes, Data) run a local, loopback-sealed engine on your machine. That engine can reach anything on your laptop or LAN, but it deliberately cannot reach into your cloud — it holds no cloud credentials and sits behind a sealed boundary. A connector bridges that gap without ever handing a cloud secret to the app.

Concretely, a connector is:

  • A single function that speaks a small tools-over-HTTP protocol (/healthz, /manifest, /tools, /call, /call-stream).
  • Pure Python, source-only — no binaries, no .pyc, no vendored dependencies. Everything runs on the bare cloud runtime using only the standard library. Even the wire protocols it can't get from stdlib (SigV4 signing, RS256 JWT signing, RFC-6455 WebSocket framing, the Postgres/Redis/Mongo/Bolt wire protocols) are hand-rolled in Python. The packaging script skips byte-compiled files (.pyc and __pycache__) and syntax-checks every .py file without writing bytecode, so a published zip holds source only.
  • Deployed in your cloud, behind your cloud's native API-key gateway (API Gateway, a Cloud Function with an API key, an Azure Function key). The gateway validates the x-api-key header before the request reaches your function — the connector itself performs no authentication.
  • Positioned where the resource lives — e.g. the kubernetes connector runs inside the cluster's VPC and talks to the API server over raw HTTPS, so a cluster with a private endpoint becomes reachable from your local app without a VPN or a kubeconfig on your laptop.

Note

A connector is the engineering analog of an AI cell: same tools-over-HTTP protocol, same pure-Python source-only zip delivery. Most connectors expose three tools for one service; the standalone Kubernetes connector is the exception, with 151.

Why bring-your-own-cloud

The connector model exists to keep a hard property: the OpenEng app engine never holds your cloud secret.

Credentials stay put

The cloud credential (an IAM key, a Kubernetes service-account token, a storage account key) lives only in your function's environment, in your account. The app engine is given the connector's URL and API key (plus an optional bearer token and write key) — not the underlying cloud secret.

Data stays in your account

Query results, logs, and object bytes are produced inside your cloud and returned over an HTTPS channel you own. There is no OpenEng-operated proxy in the path — the engine calls your function directly.

You control the blast radius

You choose the IAM role, the VPC, the read-only database user, and the command allowlists. A connector can do exactly what the role and env you gave it permit — nothing more.

This is why connectors are distributed as source, not a hosted service: you can read every line before you deploy it, and you host it yourself. OpenEng publishes the code and the marketplace catalog; the running function, and everything it can touch, is yours.

Deploy a connector and use it

  1. Pick a connector. Browse connectors.openeng.app. You can search it and filter by read or write type, by category, and by the OpenEng app a connector works with. A connector's page lists its tools and the environment variables it needs, and offers the download: a zip per cloud for a multi-cloud capability (Download for your cloud), or a single zip for a standalone connector. Each zip holds the Python source plus a README.md, requirements.txt, a Dockerfile, deploy.sh, and .env.example.
  2. Deploy it in your cloud. Run the function where it can reach the resource (for example, attached to the same VPC), set the environment variables from its page, and put your cloud's API-key gateway in front of it: the connector does no authentication of its own. The README in the zip walks through the deploy.
  3. Allow writes, if you want them. A connector is read-only until a request carries a valid write key. To create one, open connectors.openeng.app/write-key (Write key in the site's menu), set the encoded hash it generates as the function's WRITE_KEY environment variable, and keep the plaintext key for the app. See Read-only by default, per-request write key.
  4. Add it to an app with the gateway URL and API key, plus the plaintext write key if you made one:

If the connector later rejects the stored credentials (HTTP 401 or 403), the app asks you to re-enter them.

The connector protocol

Every connector — whatever ecosystem it fronts, whatever cloud it runs on — serves the same handful of endpoints:

PathMethodPurpose
/healthzGETLiveness probe — {"ok": true}.
/manifestGETConnector id/type, engine family, env-var requirements, static params, the visible tool list, and the read-only / write-key / streaming flags.
/toolsGETThe tool catalog — each tool's name, description, and JSON-Schema inputSchema.
/callPOSTInvoke one tool: {"name":"...","arguments":{...}} → {"result": ...}, or a non-200 with {"error": ...}.
/call-streamPOSTThe NDJSON streaming twin of /call — same body and auth, a stream of frames instead of one blob (see Streaming transport).

A call looks like this:

text
POST {base}/call
  Headers:  x-api-key: <your connector API key>
            Authorization: Bearer <token>        # optional
            x-write-key: <plaintext write key>    # optional; grants write access
  Body:     {"name":"run_query","arguments":{"query":"SELECT 1"}}

  200  {"result": { ... }}
  404  {"error": "unknown tool 'foo'"}
  403  {"error": "<connector> is in read-only mode — 'delete_x' is a mutating action and is disabled"}
  502  {"error": "<handler exception message>"}

The capability contract

Data- and terminal-facing connectors converge on three tools so the engines can drive any of them uniformly:

  • ping → {"version": "..."} — a connectivity/identity probe.
  • list_children → {"items":[...]} — one level of a browsable tree (e.g. buckets → prefixes → objects; databases → tables; shell environments → status attributes). Each item carries a label, a kind, an icon hint, and a runnable flag.
  • run_query → a normalized result (table / documents / graph / value / empty) — the connector's one command entry point. Read verbs are always allowed; write verbs are gated by the write key.

The Kubernetes connector is the deep exception: instead of three generic tools it exposes the full operator surface — list/get/delete across ~43 resource kinds, namespace overviews, pod logs, pod_exec, deployment scaling, rollout restarts, node cordon/uncordon, and a generic apply_manifest/delete_resource that works on any kind including CRDs.

Capability architecture & multi-cloud

Connectors are organized around two orthogonal axes, so one body of source serves every cloud.

Capability abstraction, not vendor names

A connector is named for the capability it provides, not the vendor product: object-storage, not "s3"; message-queue, not "SQS"; document-database, not "DynamoDB". Each capability defines one tool contract, and ships three interchangeable backends behind it:

text
impl/object-storage/
├── meta.py               # cloud-neutral env vars + static params
├── tools.py              # ping / list_children / run_query — the ONE contract
└── backends/
    ├── aws.py            # Amazon S3           (SigV4)
    ├── gcp.py            # Google Cloud Storage (OAuth2)
    └── azure.py          # Azure Blob Storage   (Shared Key / SAS / AAD)

The active backend is chosen at runtime from the cloud the package was stamped with (CONNECTOR_CLOUD, or a baked connector/_cloud.py). So "browse my object store" is one line of engine code whether the bytes live in S3, GCS, or Blob — the Azure container is even surfaced as a "bucket" so the vocabulary matches.

One source, three deploy targets

The second axis is where the function runs. Every package ships entry-point shims for all three managed runtimes plus a plain HTTP server, from one connector core:

TargetShimRuntime
AWS Lambdalambda_function.pyFunction URL, API Gateway HTTP API, or REST API proxy — all handled by lambda_response.
GCP Cloud Function (gen2) / Cloud Runmain.pyfunctions-framework / Flask, via gcp_response.
Azure Function (Python v2)function_app.pyA {*path} catch-all route, via azure_response.
Local / container / self-hostserve_httpA stdlib BaseHTTPRequestHandler on $PORT — for local testing or running the connector as a container.

The marketplace therefore offers a per-cloud download for each capability: object-storage-aws, object-storage-gcp, object-storage-azure are three catalog entries sharing one impl directory, packaged into three zips. That is how 80 capabilities × 3 clouds + 12 standalone connectors reach 252 packages.

Cloud auth cores (pure stdlib)

Each backend builds on a shared, dependency-free auth core in sdk/cloud/:

  • aws.py — SigV4 request signing (hmac/hashlib), pinned byte-for-byte to the AWS get-vanilla official test vector. Signs any S3/SQS/DynamoDB/Secrets-Manager/CloudWatch/Lambda REST call, with STS session-token support.
  • gcp.py — service-account OAuth2 via an RS256 signer written in pure Python (minimal DER parse, PKCS#1 v1.5 pad, big-integer pow), cross-checked byte-exact against openssl. Also accepts a caller-supplied access token or an API key.
  • azure.py — Storage Shared-Key (HMAC-SHA256 canonicalization) plus AAD client-credentials, plus caller-supplied bearer or SAS. The HMAC primitive is cross-checked against openssl.

Validation is labeled honestly

Every catalog entry carries a validation field. A handful are live (certified against a real or emulated backend — e.g. the AWS object-storage backend against MinIO, and the standalone DB connectors against real Postgres/MySQL/Redis/Mongo/Neo4j); the marketplace shows them as "tested". The long tail is marked spec (shown as "spec"): real, signed-REST implementation that has not yet been certified against live cloud credentials. As of September 25, 2026, 13 of the 252 packages are live and 239 are spec. Nothing is ever labeled "tested" that wasn't.

Streaming transport

Results paint incrementally instead of arriving as one blob. Alongside /call, every current connector serves POST /call-stream, which returns application/x-ndjson — one JSON frame per line:

text
{"t":"meta","seq":0,"kind":"query|list|logs|exec|generic","meta":{...}}  // 0..1, first
{"t":"chunk","seq":N,"data":{...}}                                       // 0..*  (batched)
{"t":"end","seq":M,"result":<value|null>,"trailer":{...}}                // success terminal
{"t":"error","seq":M,"error":"..."}                                      // failure terminal

A tool with a source-streaming generator (e.g. pod logs read in chunks, a live pod_exec stream) emits frames as data arrives. Every other tool is auto-reframed: its buffered result is inspected and re-emitted as batched query/list/logs/generic frames (200 rows or 64 KiB of text per chunk). So all connectors stream for free, and the lazy ones additionally win latency.

A hard transport fact shapes the design: AWS Lambda behind API Gateway and Azure Functions buffer the whole response (the Python managed runtimes can't wire-stream), while GCP Cloud Functions / Cloud Run and self-hosted serve_http truly flush frame-by-frame. The NDJSON protocol degrades cleanly: it wire-streams where the transport allows and delivers one buffered NDJSON body where it can't — and the engine parses frame-by-frame either way, so the code path is identical and a connector that moves to a streaming-capable runtime needs no change.

Reduction invariant

Concatenating a stream's frames reconstructs exactly the value /call would return. The engine relies on this: it speaks only /call-stream to connectors and, for unary RPCs, reduces the frames back into a whole value in Rust. /call remains served for external and marketplace use as the buffered fallback.

Authentication & access control

There are three independent credentials on a connector request, and they answer three different questions.

x-api-key

Can you reach this connector at all? Your cloud's API-key gateway validates it before the request reaches the function. This is the primary connectivity gate. The connector itself never checks it.

Authorization: Bearer

Optional backend authorization. The "API key WITH authorization" shape — a short-lived STS session token, GCP OAuth2 token, or AAD bearer layered on top of a base credential. Empty means the connector uses its long-lived key alone (and, in the apps, the api_key is reused as the bearer).

x-write-key

May this request mutate? The plaintext write key. Present and valid → write access for this one request; absent or wrong → read-only. This is the read/write gate.

Read-only by default, per-request write key

Every connector is read-only unless a request presents a valid write key — there is no static "read-only" env var. The mechanism is password-vs-hash so a leaked function environment can't be replayed:

  1. The operator generates a write key once (at connectors.openeng.app → Generate write key, or any 64-char secret). The generator returns two artifacts.
  2. The encoded hash (pbkdf2_sha256$<iterations>$<salt>$<derived>, PBKDF2-HMAC-SHA256, 210,000 iterations) is stored on the function as the WRITE_KEY env var.
  3. The plaintext key is entered in the OpenEng app and sent on every request as the x-write-key header.
  4. On each request the connector verifies the presented plaintext against the stored hash (constant-time) and sets a per-request write mode. A match → mutating tools are allowed; anything else → read-only.

Because the function stores only the salted hash, someone who reads the function's env and replays that hash as x-write-key still fails (pbkdf2(hash) ≠ hash). The identical algorithm runs in the browser (Web Crypto PBKDF2), so the generator page and the function agree.

When a connector is read-only for a request, mutating tools are hidden from /tools and /manifest and 403-blocked at /call — defense in depth. An unknown tool is still a 404 (looked up in the full list, not the filtered one), so read-only mode never leaks tool existence differently.

Force-exec: read-only with named exceptions

A single tool can carry a force_exec exemption. On the Kubernetes connector, K8S_FORCE_EXEC holds comma-separated pod-name prefixes that remain pod_exec-able even in read-only mode — so an operator can keep a connector globally read-only yet still shell into a named set of pods (e.g. dv06 matches every pod whose name starts with it). The exemption is checked against the target pod name before the exec runs.

Per-connector command restrictions

The Kubernetes connector also honors K8S_RESTRICTED_COMMANDS — a comma-separated blocklist of command names pod_exec refuses to run, checked in the function before the exec reaches the cluster. It parses the command with real shell tokenization (shlex): it recurses into sh -c scripts, $(...) and backtick substitutions, and scans past wrapper programs (sudo/env/xargs/timeout/…) to the real command. This is defense-in-depth, not a sandbox — cluster RBAC remains the actual security boundary.

The manifest advertises the posture

GET /manifest makes the connector's access model inspectable before any call:

text
{
  "connector": "object-storage", "type": "write", "engine": "s3",
  "readOnly": true,              // this request's effective mode (x-write-key considered)
  "readOnlyCapable": true,       // has mutating tools the write key gates
  "writeKeyConfigured": false,   // a WRITE_KEY hash is set on the function
  "writeKeyRequired": true,      // writes need a valid x-write-key
  "readOnlyRequiresRole": true,  // DB connectors: the write-key gate is defense-in-depth;
  "readOnlyRoleAdvice": "...",   //   a HARD guarantee needs a least-privilege read-only role
  "forceExec": [], "forceExecCapable": false,
  "streaming": true,
  "envVars": [...], "staticParams": [...], "tools": [...]
}

Databases can't be gated by a lexical classifier alone

For a database connector, the write-key gate and the read-verb classifier are defense-in-depth — they're defeatable by side-effecting functions (dblink, pg_write_file, INTO OUTFILE, apoc.*, admin DDL). The manifest surfaces readOnlyRequiresRole: true to say so explicitly: for a hard read-only guarantee, also connect the connector with a least-privilege read-only database role. The write key gates who may write; the role gates what the connector can do at all.

Adding a connector to an app

Adding a connector is the same three inputs everywhere: the connector's base URL, its API key, and an optional write key (plus an optional bearer). The engine probes /manifest on add to learn read-only status, the engine family, and the tool surface, then stores the credentials locally. Which apps expose connectors:

Kubernetes

A connector-backed cluster. The engine's AddConnectorCluster RPC takes {name, url, api_key, auth_token, write_key} and assigns a context id connector:<name>. An empty auth_token reuses the api_key as the bearer; an empty write_key keeps the cluster read-only. GetConnector reads the stored creds back to the local UI to pre-fill the re-auth dialog on a 401/403.

Terminal

A saved connection with kind="connector" and a connector_url, chosen via an "SSH host / Cloud connector" toggle in the connection form. ConnectorExec (and the streaming ConnectorExecStream) run a command through the connector's run_query tool — the "cloud terminal execution" path for cloud-shell, run-command, and container-exec capabilities. Credentials are write-only on save and stored in the encrypted secrets vault.

Data

A connector connection whose engine family (from the manifest — postgres, redis, neo4j, s3, …) selects the right browse tree and query panels. The Data engine drives list_children for the tree and run_query for the playground, streaming rows/documents/graph progressively.

Git

None — intentionally. Git ("gate") has no repo connectors by design.

In all three apps the app engine stores api_key, auth_token, and write_key on your machine and forwards them as x-api-key / Authorization / x-write-key on each request. Terminal keeps them write-only, in its encrypted vault. Kubernetes and Data return them to the app over the sealed loopback to pre-fill the edit and re-auth dialogs (Kubernetes the API key, bearer token and write key; Data the API key and bearer token), the same trust domain that entered them; they go nowhere else.

Tip

A connector with no write key is fully usable read-only. Add the write key only for the connections that should mutate state, and give the plaintext key only to the users who should have write access — the same key controls whether list_children/run_query may write and whether mutating tools are offered at all.

Capability reference

The 80 multi-cloud capabilities span thirteen categories. Each is shipped for AWS, GCP, and Azure behind one contract; the per-cloud service each fronts is listed in the marketplace.

CategoryCapsRepresentative capabilities
Networking12dns-zone, load-balancer, cdn, api-gateway, virtual-network, subnet, static-ip, nat-gateway, vpn-gateway, firewall-rules, private-endpoint, waf
Compute10compute-instances, auto-scaling, machine-image, container-registry, container-service, managed-kubernetes, serverless-functions, app-platform, batch-compute, artifact-repository
Databases8managed-database-instance, document-database, key-value-store, wide-column-database, graph-database, time-series-database, ledger-database, cache-cluster
Execution7cloud-shell, compute-run-command, container-exec, serverless-invoke, batch-submit, job-scheduler, workflow-orchestration
Messaging6message-queue, message-broker, event-bus, event-stream, pubsub-topic, notification-service
Governance6budget, cost-usage, policy-definition, tag-inventory, resource-group, service-quota
Analytics6analytics-query, data-warehouse, data-catalog, etl-pipeline, search-index, vector-database
Storage5object-storage, block-volume, file-share, backup-vault, volume-snapshot
Observability5log-query, metric-query, trace-query, alarm-inventory, audit-log
Security4secret-store, key-vault, certificate-store, compliance-finding
Identity4iam-policy, iam-role, iam-user, service-account
AI/ML4ml-endpoint, ml-model-registry, ml-training-job, notebook-instance
Config3parameter-store, config-store, feature-flags

Headline capabilities

object-storage

Browse any object store as buckets → /-folded prefixes → objects and read object bytes, over one contract — Amazon S3, Google Cloud Storage, Azure Blob, or any S3-compatible store (MinIO/Ceph/R2). run_query verbs: LIST/GET/HEAD read; PUT/DELETE are blocked without a write key.

kubernetes (standalone)

The full operator surface — 151 tools, 51 mutating — running inside the cluster VPC, talking to the API server over raw HTTPS with only K8S_API_SERVER/K8S_TOKEN/K8S_CA_CERT. Real pod_exec over a hand-rolled WebSocket, pod logs, scaling, rollout restarts, node cordon, and generic apply/delete for any kind including CRDs.

cloud-shell

A managed shell environment you can list, start, stop, and exec into — AWS CloudShell, GCP Cloud Shell, or Azure Cloud Shell — driven from the Terminal app. run_query verbs: LIST/STATUS read; START/STOP/RUN write.

Database capabilities

document-database, key-value-store, wide-column, graph, time-series, vector, and ledger — each browsable as a tree and queryable through run_query, with the Data engine picking UI panels from the manifest engine family.

secret-store / key-vault

Read-only browse of secrets and keys — AWS Secrets Manager / GCP Secret Manager / Azure Key Vault. Read connectors: any write verb is refused regardless of write key.

message-queue / event-stream

Peek queues and streams — SQS, Pub/Sub, Azure Queue / Service Bus, Kinesis, and friends — for inspection from Data and Terminal without draining them.

Standalone connectors

Twelve connectors target a specific engine directly over its native wire protocol (hand-rolled, stdlib-only) rather than a cloud REST API: kubernetes, postgres, mysql, cockroach, redis, mongodb, cassandra, neo4j, kafka, s3, gcs, and dynamodb. The database connectors implement the Data-engine contract with three-layer read-only enforcement (transport guard, audited classifier defaulting to WRITE, and normalized server-side permission errors) and connect to self-hosted, managed, and cloud-compatible variants alike — e.g. neo4j speaks native Bolt (5.x down to 4.1) or the HTTP transaction API from whichever URL you paste.

Security model

Bring your own cloud

The function runs in your account, under an IAM role you scope. OpenEng never operates it and never sees its credentials.

Local credentials only

The connector's API key, bearer, and write key are stored locally by the app engine (encrypted vault in Terminal). Between the app and the engine they travel only over the sealed loopback; the engine sends them to your connector's gateway on each request, and nowhere else.

Read-only by default

No write key → read-only. Mutating tools are hidden from discovery and 403-blocked at call time. Writes require a per-request key that can't be replayed from the stored hash.

Least-privilege belongs to you

For a hard boundary, pair the connector with a read-only DB role, a scoped IAM role, force-exec pod allowlists, and command restrictions. The connector's own gates are defense-in-depth on top.

Two properties are worth restating because they are load-bearing. First, the gateway authenticates, not the connector — the x-api-key check happens at your cloud's edge, so once you put an API-key gateway in front of the function, an unauthenticated request never reaches your code. The deploy script doesn't create that gateway, and the connector does no authentication of its own. Second, the write-key hash can't be replayed — only a PBKDF2 hash lives in the function env, and presenting that hash as the plaintext key fails verification.

Build your own connector

The marketplace ships a BYOC (Bring Your Own Code) starter — public/byoc/aws.zip, linked from the site — for writing a connector against a resource no published capability covers. It contains the full sdk/ framework verbatim plus a one-file stub:

text
from sdk import Connector, Tool, KIND_READ, obj, str_p

def example_tool(args):
    return {"echo": args.get("text", "")}   # TODO: your logic

CONNECTOR = Connector(
    id="my-connector",
    type=KIND_READ,                          # read-only; use KIND_WRITE for mutating tools
    tools=[
        Tool("example_tool", "Describe what this does.",
             obj({"text": str_p("Some input.")}), example_tool),
    ],
)

You get the whole protocol for free — /manifest, /tools, /call, /call-stream, the read-only + write-key gate, and the NDJSON auto-reframer — by declaring a Connector and its Tools. Mark a tool mutating=True and it becomes write-gated automatically. Provide a stream_handler generator to stream at the source; otherwise your buffered handler is auto-reframed. Deploy the same way as any published connector: as a Lambda in-VPC or a container, fronted by an API-key gateway. The stub ships with a README, Dockerfile, and requirements so the deploy path matches the reference connectors exactly.

Tip

To ship a multi-cloud capability instead of a single connector, follow the impl/object-storage/ shape: a tools.py contract plus backends/{aws,gcp,azure}.py, each building on the matching sdk/cloud/ auth core. The build pipeline expands one impl directory into three per-cloud catalog entries and three zips.