Secrets Management
Chronon provides a secrets management layer that lets you store sensitive credentials in cloud vaults and reference them from configuration — without ever embedding secret values in code or team configs.
The feature works at two levels:
- Chronon engine — resolves vault URIs into environment variables at driver startup, making secrets available to the running Spark job.
- Zipline Hub — performs the same vault resolution before job submission, and additionally interpolates resolved environment variables into Spark conf values.
Vault URI Convention
Wherever you can set an environment variable, you can store a vault reference by appending _VAULT_URI to the target variable name:
{SECRET_NAME}_VAULT_URI = <cloud vault URI>At runtime the secret is fetched and exposed as:
{SECRET_NAME} = <resolved secret value>Supported vault backends and their URI formats:
| Backend | URI example |
|---|---|
| Azure Key Vault | https://{vault-name}.vault.azure.net/secrets/{secret-name} |
| AWS Secrets Manager | arn:aws:secretsmanager:{region}:{account}:secret:{name} |
| GCP Secret Manager | projects/{project}/secrets/{secret} or projects/{project}/secrets/{secret}/versions/{version} |
GCP defaults to latest when no version is specified.
Chronon Engine: Environment Variable Resolution
When BatchNodeRunner or KVUploadNodeRunner starts, it automatically calls the secret resolver on the current environment before the job executes. Any *_VAULT_URI variable is resolved to its secret value and the result is passed to the Online API layer.
Example — Azure Key Vault:
Set the vault URI before submitting the job (e.g., in your cluster environment or via spark-submit --conf spark.executorEnv.*):
export SNOWFLAKE_PRIVATE_KEY_VAULT_URI="https://demo-service-writer-pkey.vault.azure.net/secrets/snowflake-private-key"At driver startup, the resolver detects SNOWFLAKE_PRIVATE_KEY_VAULT_URI, fetches the secret from Azure Key Vault, and makes SNOWFLAKE_PRIVATE_KEY available to the job — without it ever appearing in the submitted configuration.
Safety rules:
- If
SNOWFLAKE_PRIVATE_KEYis already present in the environment, vault resolution for its_VAULT_URIcounterpart is skipped. This allows overriding secrets locally without changing vault contents. - Fetch failures are non-fatal: the resolver logs a warning and leaves the variable unresolved, allowing a downstream layer (e.g., Hub fails to resolve, driver tries after) to retry with its own credentials.
Zipline Hub: Resolution + Spark Conf Interpolation
The Hub performs the same vault URI resolution step before submitting a job. It also adds a second step: interpolating resolved environment variables into Spark conf values using {VAR_NAME} placeholders.
This two-step chain lets a single vault URI flow all the way into a Spark configuration property:
OC_CREDENTIAL_VAULT_URI (vault URI in env)
→ OC_CREDENTIAL (resolved secret in env)
→ {OC_CREDENTIAL} placeholder in spark conf → actual secret value in SparkConfiguration Example
The following shows a team configuration that uses this pattern. The vault URI is declared in env, and the placeholder is used in the Spark catalog credential:
azure = Team(
...
node=NodeProperties(
env={
# Vault URI → resolves to OC_CREDENTIAL env var
"OC_CREDENTIAL_VAULT_URI": "https://dev-zipline-secrets.vault.azure.net/secrets/oc-catalog-credential/9dfce586bacb4c67b68514a9bdfb7a52",
# Other non-secret env vars
"SNOWFLAKE_JDBC_URL": "jdbc:snowflake://VEJLULX-AZURE.snowflakecomputing.com/?user=demo_batch_service&db=Demo&schema=public&warehouse=demo_wh",
},
),
conf=ConfigProperties(
common={
**OpenCatalogConfiguration({
"spark.sql.catalog.spark_catalog.uri": "https://vejlulx-azure-oc.snowflakecomputing.com/polaris/api/catalog",
# {OC_CREDENTIAL} is replaced with the resolved secret at submission time
"spark.sql.catalog.spark_catalog.credential": "{OC_CREDENTIAL}",
"spark.sql.catalog.spark_catalog.warehouse": "demo-v2",
}),
},
),
)The Hub resolves OC_CREDENTIAL_VAULT_URI → OC_CREDENTIAL, then substitutes {OC_CREDENTIAL} in the Spark conf before the job is submitted. The Spark driver never sees the vault URI — only the final credential value.
Placeholder Syntax
Placeholders follow the pattern {VAR_NAME} where VAR_NAME must consist of uppercase letters, digits, and underscores (and must start with a letter or underscore). A single conf value can contain multiple placeholders:
"spark.chronon.jdbc.url": "jdbc://{DB_HOST}:{DB_PORT}/mydb"Placeholders that cannot be resolved (because the corresponding env var was not set or vault resolution failed) are left as-is in the submitted configuration.
Precedence and Layering
The two-layer design means each layer can independently retry resolution:
- The Hub resolves vault URIs using its own service identity and injects the secrets into the env before job submission. It also performs placeholder interpolation in Spark conf.
- The Chronon engine resolves any remaining
*_VAULT_URIentries at driver startup using the executor's identity.
This is useful when the executor has access to secrets that the Hub service account does not (e.g., tenant-specific vaults). In that case, leave the _VAULT_URI variable in the env; the Hub will log a warning and leave it for the driver to resolve.
Migrating From Hardcoded Secrets
Replace hardcoded values with the vault URI + placeholder pattern in two steps:
Before:
env={
"SNOWFLAKE_VAULT_URI": "https://demo-service-writer-pkey.vault.azure.net/secrets/snowflake-private-key",
},
conf={
"spark.sql.catalog.spark_catalog.credential": "XtyCirtE0/o3pcTMdkLCh7LXVno=:i++cOG/+...",
}After:
env={
"SNOWFLAKE_PRIVATE_KEY_VAULT_URI": "https://demo-service-writer-pkey.vault.azure.net/secrets/snowflake-private-key",
"OC_CREDENTIAL_VAULT_URI": "https://dev-zipline-secrets.vault.azure.net/secrets/oc-catalog-credential/9dfce586bacb4c67b68514a9bdfb7a52",
},
conf={
"spark.sql.catalog.spark_catalog.credential": "{OC_CREDENTIAL}",
}Note the rename: SNOWFLAKE_VAULT_URI → SNOWFLAKE_PRIVATE_KEY_VAULT_URI. The suffix before _VAULT_URI must match the environment variable name you want the resolved secret to appear under.