Snowflake edge connector

Run local or VPC-hosted RLM analysis against Snowflake while Snowflake credentials remain in a trusted broker. Generated Python talks to that broker with a short-lived capability; it never receives the Snowflake account credential or driver configuration.

The connector is a deployment preview. It requires the mrl Edge image, a customer-provisioned Snowflake service identity, and an explicit local SQL policy.

What lives where

ModelRelay owns the Snowflake connector, authentication, SQL policy validation, hard result limits, and operator commands. Droste remains the generated-code runtime: it receives only a connector-neutral database capability and uses schema and query operations. Snowflake-specific credentials and policy do not belong in Droste.

flowchart LR
    M[ModelRelay model calls] --> R[Droste runner container]
    R -->|Bearer capability and schema or query| B[Trusted mrl Snowflake broker]
    B -->|Local Snowflake SQL policy| V[Validator]
    V -->|Normalized SELECT| S[Snowflake]

The broker returns rows only to the local runner. As with other local RLM sources, generated code can include selected or derived values in model subcalls or the final answer; those values then follow the normal ModelRelay request path. Snowflake credentials and authentication tokens are never sent to ModelRelay.

Quick start

Start from the deployment example in deploy/snowflake-edge:

cp deploy/snowflake-edge/snowflake.json.example \
  deploy/snowflake-edge/snowflake.json

export MODELRELAY_API_KEY='mr_sk_...'
export MODELRELAY_SNOWFLAKE_PRIVATE_KEY_FILE="$PWD/snowflake_rsa_key.p8"
export MODELRELAY_SNOWFLAKE_BROKER_TOKEN="$(openssl rand -hex 32)"

docker build -f Dockerfile.edge-snowflake \
  -t modelrelay/mrl-edge-snowflake:2.4.0 .
docker compose -f deploy/snowflake-edge/compose.yaml up -d broker

Test authenticated readiness, then run RLM:

docker compose -f deploy/snowflake-edge/compose.yaml exec broker \
  /mrl snowflake test-connection --config /run/modelrelay/snowflake.json

MODELRELAY_RLM_QUERY='Compare weekly revenue and explain anomalies' \
  docker compose -f deploy/snowflake-edge/compose.yaml \
  --profile run run --rm runner

The two services must stay separate. Only broker mounts snowflake.json and the private key; runner receives the broker URL and short-lived capability token.

Snowflake setup

Run deploy/snowflake-edge/setup.sql with an administrator role after replacing its example identifiers. The minimum role needs:

  • USAGE on one warehouse, the approved database, and approved schemas.
  • Explicit SELECT grants on only the approved tables and views. Avoid ALL and FUTURE grants, and mirror every grant in policy.tables.allowlist.
  • no create, write, stage, procedure, or function privileges.

Use secure views when base-table definitions or sensitive columns must not be exposed. Snowflake continues to enforce secure-view, masking, and row-access policies for the service role. Schema discovery reports accessible tables, columns, table types, and whether a view is secure; it omits view definitions and does not require access to POLICY_REFERENCES.

Keep Snowflake statement timeouts on the service user or warehouse as a second boundary. The effective Snowflake timeout is the lowest nonzero applicable timeout. A resource monitor can additionally suspend the connector warehouse when its credit budget is reached.

Connector configuration

The JSON configuration is owned by the trusted broker. The caller of /v1/sql cannot choose or relax its policy.

{
  "snowflake": {
    "account": "organization-account",
    "user": "MODELRELAY_SERVICE",
    "warehouse": "MODELRELAY_RLM_WH",
    "database": "ANALYTICS",
    "schema": "RLM_APPROVED",
    "role": "MODELRELAY_RLM_READONLY",
    "auth": {
      "type": "key_pair",
      "key_pair": {
        "private_key_file": "/run/secrets/snowflake_private_key"
      }
    },
    "limits": {
      "max_rows": 1000,
      "max_response_bytes": 1048576,
      "max_timeout_ms": 5000,
      "max_queued_timeout_ms": 5000
    }
  },
  "tenant_id": "customer-tenant",
  "source_id": "finance-warehouse",
  "policy": {
    "dialect": "snowflake",
    "read_only": true,
    "limits": {
      "default_limit": 1000,
      "max_limit": 1000,
      "timeout_ms": 5000
    },
    "tables": {
      "allowlist": ["ANALYTICS.RLM_APPROVED.MONTHLY_REVENUE"]
    },
    "aggregations": {
      "allowed": true,
      "functions": ["count", "sum", "avg", "min", "max"]
    }
  }
}

policy is the authoritative structured SQL policy. Its dialect must be snowflake and read_only must be true; every raw query is validated locally before execution. tenant_id is required and source_id is an optional attribution label. The independent snowflake.limits are host-enforced hard caps. A policy cannot raise them. Requests exceeding the row, encoded-byte, execution-time, or queued-time limit fail closed.

Authentication methods

Set exactly one auth.type and its matching object:

Method Configuration Secret semantics
workload_identity provider is AWS, AZURE, GCP, or OIDC; optional impersonation_path, entra_resource, or token_file OIDC requires token_file; the file is mounted only in the broker
oauth_client_credentials client_id_env, client_secret_env, HTTPS token_url, and optional scope The first two fields name environment variables containing client credentials
key_pair private_key_file and optional passphrase_env The broker reads an encrypted or unencrypted PKCS8 PEM file; the optional environment variable contains its passphrase

The Compose example uses an unencrypted private key. For an encrypted PKCS8 key, set passphrase_env to a variable name inside key_pair and inject that variable only into the broker. Never mount the key, OIDC token file, or OAuth secrets into the runner.

For example, workload identity with an AWS runtime identity is:

{
  "auth": {
    "type": "workload_identity",
    "workload_identity": {"provider": "AWS"}
  }
}

OAuth client credentials are:

{
  "auth": {
    "type": "oauth_client_credentials",
    "oauth_client_credentials": {
      "client_id_env": "SNOWFLAKE_OAUTH_CLIENT_ID",
      "client_secret_env": "SNOWFLAKE_OAUTH_CLIENT_SECRET",
      "token_url": "https://identity.example.com/oauth2/token",
      "scope": "session:role:MODELRELAY_RLM_READONLY"
    }
  }
}

Password authentication is not supported. Prefer workload identity where the deployment platform and Snowflake account support it, then OAuth client credentials, then a rotated key pair. The Go connector does not support GCP regional endpoints for workload identity, and Azure sovereign-cloud workload identity is not supported.

Broker and runner commands

Start the trusted broker:

mrl snowflake serve \
  --config /run/modelrelay/snowflake.json \
  --listen 0.0.0.0:8081 \
  --broker-token-env MODELRELAY_SNOWFLAKE_BROKER_TOKEN

Run generated code in a separate process or container:

mrl rlm 'Analyze the approved Snowflake data' \
  --snowflake-broker-url http://broker:8081/v1/sql \
  --snowflake-broker-token-env MODELRELAY_SNOWFLAKE_BROKER_TOKEN

The broker defaults to 127.0.0.1:8787. Its API is deliberately small:

Endpoint Authentication Purpose
GET /healthz None Process/config liveness only; never returns secrets or rows
GET /readyz Bearer capability Snowflake readiness check
POST /v1/sql Bearer capability Policy-validated schema and query operations

Use GET /healthz for process liveness and mrl snowflake test-connection --config PATH or authenticated GET /readyz for Snowflake readiness. Inspect the approved schema with mrl snowflake schema --config PATH.

Production controls

  • Run the broker and runner as nonroot in separate containers or security principals. Never mount the Snowflake config, token file, or private key into the runner.
  • Use a read-only root filesystem, a bounded /tmp tmpfs, dropped Linux capabilities, and no-new-privileges.
  • Restrict broker ingress to the runner network. Restrict egress to Snowflake, required cloud identity or OAuth endpoints, and ModelRelay from the runner.
  • Rotate the broker capability per deployment or job. Do not reuse it as a Snowflake or ModelRelay credential. The Compose quickstart passes it as an environment value visible in Docker metadata; production orchestrators should inject it directly without a checked-in env file.
  • Search Snowflake query history by the connector’s query tag for attribution and incident response. Query tags contain identifiers only, never SQL results or credentials.

Current limitations

Provisioning the Snowflake account, warehouse, role, identity-provider integration, private connectivity, resource monitor, and policy-protected test fixtures remains customer-owned. Validate private-link DNS and egress from the target VPC before onboarding. General availability also depends on completing a design-partner deployment with secure views or masking/row-access policies in place.