Connectors
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 credentials and data never leave your account; the app engine holds only the connector's URL and API key. The marketplace at connectors.openeng.app ships 252 ready packages — 80 capabilities across AWS, GCP and Azure plus 12 standalone database connectors — 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. CI enforces that no binary or byte-compiled file ever lands in a published zip. - 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-keyheader before the request reaches your function — the connector itself performs no authentication. - Positioned where the resource lives — e.g. the
kubernetesconnector 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.
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 — 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.
The connector protocol
Every connector — whatever ecosystem it fronts, whatever cloud it runs on — serves the same handful of endpoints:
| Method | Path | Purpose |
|---|---|---|
GET | /healthz | Liveness probe — {"ok": true}. |
GET | /manifest | Connector id/type, engine family, env-var requirements, static params, the visible tool list, and the read-only / write-key / streaming flags. |
GET | /tools | The tool catalogue — each tool's name, description, and JSON-Schema inputSchema. |
POST | /call | Invoke one tool: {"name":"...","arguments":{...}} → {"result": ...}, or a non-200 with {"error": ...}. |
POST | /call-stream | The 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:
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 arunnableflag.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:
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:
| Target | Shim | Runtime |
|---|---|---|
| AWS Lambda | lambda_function.py | Function URL, API Gateway HTTP API, or REST API proxy — all handled by lambda_response. |
| GCP Cloud Function (gen2) / Cloud Run | main.py | functions-framework / Flask, via gcp_response. |
| Azure Function (Python v2) | function_app.py | A {*path} catch-all route, via azure_response. |
| Local / container / self-host | serve_http | A 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 catalogue 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 AWSget-vanillaofficial 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-integerpow), cross-checked byte-exact againstopenssl. 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 againstopenssl.
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 long tail is marked spec-pending-credentials: real, signed-REST implementation that has not yet been certified against live cloud credentials. Nothing is ever labelled "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:
{"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.
/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:
- The operator generates a write key once (at
connectors.openeng.app → Generate write key, or any 64-char secret). The generator returns two artefacts. - The encoded hash (
pbkdf2_sha256$<iterations>$<salt>$<derived>, PBKDF2-HMAC-SHA256, 210,000 iterations) is stored on the function as theWRITE_KEYenv var. - The plaintext key is entered in the OpenEng app and sent on every request as the
x-write-keyheader. - 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 honours 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:
{
"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": [...]
}
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. GetConnectorCluster 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 as write-only secrets (the Terminal engine keeps them in its encrypted vault) and forwards them as x-api-key / Authorization / x-write-key on each request. They never round-trip to the UI except to pre-fill a re-auth dialog, which stays on the loopback seal boundary — the same trust domain that entered them.
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.
| Category | Caps | Representative capabilities |
|---|---|---|
| Networking | 12 | dns-zone, load-balancer, cdn, api-gateway, virtual-network, subnet, static-ip, nat-gateway, vpn-gateway, firewall-rules, private-endpoint, waf |
| Compute | 10 | compute-instances, auto-scaling, machine-image, container-registry, container-service, managed-kubernetes, serverless-functions, app-platform, batch-compute, service-quota |
| Databases | 8 | managed-database-instance, document-database, key-value-store, wide-column-database, graph-database, time-series-database, vector-database, ledger-database |
| Execution | 7 | cloud-shell, compute-run-command, container-exec, serverless-invoke, batch-submit, job-scheduler, workflow-orchestration |
| Messaging | 6 | message-queue, message-broker, event-bus, event-stream, pubsub-topic, notification-service |
| Governance | 6 | audit-log, budget, cost-usage, policy-definition, tag-inventory, resource-group |
| Analytics | 6 | analytics-query, data-warehouse, data-catalog, etl-pipeline, search-index, feature-flags |
| Storage | 5 | object-storage, block-volume, file-share, backup-vault, volume-snapshot |
| Observability | 5 | log-query, metric-query, trace-query, alarm-inventory, notebook-instance |
| Security | 4 | secret-store, key-vault, certificate-store, compliance-finding |
| Identity | 4 | iam-policy, iam-role, iam-user, service-account |
| AI/ML | 4 | ml-endpoint, ml-model-registry, ml-training-job, firewall-rules |
| Config | 3 | parameter-store, config-store, private-endpoint |
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) and forwarded on each request. They stay on the loopback seal boundary.
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 an unauthenticated request never reaches your code. 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:
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.
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 catalogue entries and three zips.