Skip to Content

Auth Setup

The Zeotap cloud control plane reverse-proxies UI traffic into your install. To authenticate at the SPCS public ingress, the cloud needs a Snowflake identity it can sign requests as. This page explains why that identity is a separate proxy user and role, what GENERATE_PROXY_KEYPAIR does, and how to rotate or harden the credential.

Why a separate proxy user and role

Snowflake Native Apps cannot create users, roles, or grants on the consumer’s account — those account-level identity privileges are an explicit security boundary. Anything that authenticates against your account has to be authored by you, the consumer, with ACCOUNTADMIN.

The cloud control plane authenticates as a Snowflake service user with a registered RSA keypair. The user, the role granted to it, and the keypair’s public side are all created by post-install-auth.sql. The private side of the keypair never touches the operator’s filesystem; it lives only inside the application (in APP_DATA.CLOUD_CREDENTIALS) and the cloud’s encrypted credential store.

-- From post-install-auth.sql CREATE USER IF NOT EXISTS ${BRAND_UPPER}_PROXY_USER TYPE = SERVICE DEFAULT_ROLE = ${BRAND_UPPER}_PROXY_ROLE COMMENT = 'Cloud-proxy service user for ${BRAND_DISPLAY_NAME} Native App reverse-proxy'; CREATE ROLE IF NOT EXISTS ${BRAND_UPPER}_PROXY_ROLE; GRANT ROLE ${BRAND_UPPER}_PROXY_ROLE TO USER ${BRAND_UPPER}_PROXY_USER; GRANT APPLICATION ROLE ${BRAND_UPPER}.APP_PUBLIC TO ROLE ${BRAND_UPPER}_PROXY_ROLE;

TYPE = SERVICE skips the password-rotation requirements for human users and forbids interactive sign-in entirely; auth is exclusively via the registered RSA key. The proxy role carries one application-role grant — APPLICATION ROLE ${BRAND}.APP_PUBLIC — which is what lets the cloud-proxied requests reach the procedures and services exposed by the app. The role has no privileges on your other databases, schemas, or warehouses; it can only call into the Zeotap application sandbox.

What GENERATE_PROXY_KEYPAIR does

The procedure runs inside the application sandbox and:

  1. Generates an RSA-2048 keypair using the runtime’s CSPRNG.
  2. Writes the private key into APP_DATA.CLOUD_CREDENTIALS. This row is invisible to your other Snowflake roles — only the application’s own procs can read it.
  3. Returns the public key as a base64-encoded SubjectPublicKeyInfo string.

The post-install script captures the public key and immediately registers it on the proxy user:

ALTER USER ${BRAND_UPPER}_PROXY_USER SET RSA_PUBLIC_KEY = '<base64 public key>';

It then calls REGISTER_WITH_CLOUD, which reads the private key out of CLOUD_CREDENTIALS, builds a JSON credential containing private_key_pem plus the proxy user and role names, HMAC-signs the body with a register secret minted on first call, and POSTs the result to the Zeotap cloud through the signalsmith_sf_api_access EAI.

The cloud receives the JSON and immediately encrypts it at rest with an AEAD secret box before inserting the row into native_app_instances.auth_credential. From that point on, every UI request the cloud reverse-proxies into your install fetches the row, decrypts it in memory, mints a JWT signed with the private key, and discards the JWT after the request completes. The private key never leaves the cloud’s encryption boundary in the clear.

The public-key half on the proxy user is what Snowflake uses to validate every JWT the cloud presents at the SPCS public ingress. If the keypair stops matching, every proxied request returns 401.

Optional hardening: NETWORK POLICY on the proxy user

The keypair alone is the only auth factor for the proxy user. If a private key leaked, an attacker could authenticate from any IP. The cloud’s AEAD encryption mitigates at-rest leakage, but in-flight the credential is bearer-token equivalent until you attach a NETWORK POLICY.

In production, attach a network policy that allows only Zeotap’s cloud egress IP range:

CREATE NETWORK POLICY ${BRAND_UPPER}_PROXY_NETPOL ALLOWED_IP_LIST = ('<cloud-egress-cidr>'); ALTER USER ${BRAND_UPPER}_PROXY_USER SET NETWORK_POLICY = ${BRAND_UPPER}_PROXY_NETPOL;

The post-install script doesn’t do this automatically because it would need the cloud’s per-region egress CIDR baked in. Those CIDRs are published in the Marketplace listing and on the Zeotap status page; copy the right one for your region and apply the policy after the install has been claimed and is running.

Rotation

Re-run post-install-auth.sql to rotate the keypair. The script’s structure is rotation-safe:

  • GENERATE_PROXY_KEYPAIR replaces the existing CLOUD_CREDENTIALS row.
  • ALTER USER ... SET RSA_PUBLIC_KEY overwrites the previously registered public key.
  • REGISTER_WITH_CLOUD re-POSTs the new credential and the cloud re-encrypts the row in native_app_instances.auth_credential.

Ongoing requests through the proxy will fail briefly (a few hundred milliseconds) between the ALTER USER and the cloud-side update — the in-flight requests will see a fleeting 401 as the public key changes ahead of the cloud’s row. New requests succeed once the cloud’s row is updated.

Rotate the keypair if you suspect the cloud-side credential has leaked, on a schedule that matches your regular service-account rotation, or after any operator handover that changed who knew the bootstrap secret.

What lives where after setup

MaterialLives inLifetime
Private keyAPP_DATA.CLOUD_CREDENTIALS (app-only) and the cloud’s AEAD-encrypted credential store.Until the next rotation.
Public keyThe proxy user’s RSA_PUBLIC_KEY attribute.Until the next rotation.
Per-request JWTCloud memory only.Discarded after each proxied request.
Register secretAPP_DATA.CLOUD_CREDENTIALS and the cloud’s register_secret column.Until the next rotation; protects re-register calls.

The operator’s filesystem holds nothing — the keypair is generated inside the application and shipped to the cloud via the EAI without ever rendering to a console.

Last updated on