How does ConfigProvider (e.g. FileConfigProvider) let you keep secrets out of connector configs, and how does the ${...} indirection work?
answer
- ${providerName:path:key} indirection
- config.providers + config.providers.<n>.class
- FileConfigProvider / EnvVar / Vault / Secrets Manager
- resolved on worker at start; config topic keeps ${...}
- secret file must exist on every worker
basics
~10 sA ConfigProvider resolves ${provider:[path:]key} placeholders at runtime so secrets aren't stored literally in connector configs. You register a provider in the worker config (e.g. config.providers=file with FileConfigProvider) and reference values like ${file:/secrets.properties:db.password}.
solid answer
~40 sConnect supports externalized secrets via the `ConfigProvider` interface. You register providers in the worker config: `config.providers=file` plus `config.providers.file.class=org.apache.kafka.common.config.provider.FileConfigProvider`. Then anywhere in a connector config you write a placeholder `${file:/opt/secrets/creds.properties:db.password}` — the syntax is `${providerName:path:key}`. At runtime the worker calls the provider with that path and key and substitutes the resolved value, so the literal secret never lives in the connector config stored in the internal config topic or returned by the REST API (which shows the unresolved `${...}`). FileConfigProvider reads a properties file on the worker's local filesystem; other providers integrate with Vault, AWS/GCP secret managers, or environment variables (`EnvVarConfigProvider`). Providers can also support TTL-based reloading via `ConfigChangeCallback` so connectors restart when a secret rotates.
go deeper
Know you can write ${file:path:key} so secrets aren't typed literally into the connector config.
Register a provider in worker config and explain that the config topic keeps the placeholder, not the secret.
Reason about per-worker availability, rotation via TTL, and choosing managed providers (Vault/secret managers).
Architect centralized secret management, rotation, audit, and least-privilege secret access across a Connect cluster.
**The problem:** Connector configs routinely contain secrets — database passwords, broker SASL credentials, API tokens. In distributed mode those configs are stored in the internal **config topic** and are retrievable via the REST API (`GET /connectors/{name}/config`). Storing plaintext secrets there means anyone with topic or REST access reads them. **The abstraction:** `org.apache.kafka.common.config.provider.ConfigProvider` is a pluggable interface. A provider knows how to fetch secret values from some backing store given a *path* and a *key*. Connect ships `FileConfigProvider`, `DirectoryConfigProvider`, and `EnvVarConfigProvider`; vendors/community add Vault, AWS Secrets Manager, GCP Secret Manager, Kubernetes, etc. **Registration (worker config):** ``` config.providers=file,vault config.providers.file.class=org.apache.kafka.common.config.provider.FileConfigProvider config.providers.vault.class=...VaultConfigProvider config.providers.vault.param.address=https://vault:8200 ``` `config.providers` lists logical names; each `config.providers.<name>.class` names the implementation; `config.providers.<name>.param.<x>` passes provider-specific params. **The indirection syntax:** In any connector (or even worker) config value you write `${<name>:<path>:<key>}`. For FileConfigProvider: `${file:/opt/connect/secrets/db.properties:password}` means *use the provider named `file`, open file `/opt/connect/secrets/db.properties`, return the value of key `password`*. For EnvVarConfigProvider the path is typically empty: `${env::DB_PASSWORD}` (or `${env:DB_PASSWORD}` depending on version). Some providers ignore the path; the three-part shape is `${name:path:key}` and a missing path leaves an empty segment. **Resolution timing and where the secret lives:** Substitution happens **on the worker when the connector is instantiated/started**, not when the config is submitted. The stored config (config topic) and REST responses keep the literal `${...}` placeholder — that's the whole point: the secret is never persisted in Connect's state. The provider reads the actual secret from its store at runtime on each worker that runs the connector, so the secret file/secret-manager entry must be present on every worker. **Rotation / TTL:** Some providers return a TTL with each value. Connect can schedule a config-reload (and connector restart) when the TTL expires via the `ConfigChangeCallback`/`subscribe` mechanism, enabling secret rotation without manual reconfiguration. FileConfigProvider itself doesn't watch the file unless wired for it; managed providers commonly do. **Edge cases / gotchas:** - The secret file must exist on *every* worker node and be readable by the Connect process — a missing file on one worker causes that worker to fail starting the connector. - File permissions matter: protect the properties file (chmod 600) — Connect just reads it. - `${...}` is resolved for connector configs and (depending on version) worker configs too; not all positions support it across all versions. - If a provider name in `${name:...}` isn't registered, the value is left as the literal string, often causing a confusing downstream auth failure rather than a clear error. **Security value:** This decouples secret storage from Connect. With a managed provider you get centralized rotation, audit, and access control; even with plain FileConfigProvider you keep secrets off the config topic and out of REST output and source control.
- After using ${file:...}, what does GET /connectors/{name}/config return for the secret?The literal placeholder string, e.g. ${file:/secrets:password}, not the resolved value. Secrets stay out of REST output and the config topic.
- Where and when is the placeholder resolved?On each worker that runs the connector, at connector instantiation/start time — so the referenced secret store must be reachable from every worker.
- How can secret rotation trigger a reload?Providers can return a TTL; Connect uses ConfigChangeCallback/subscribe to restart the connector when the value expires, picking up the rotated secret.
saying these in an interview costs you the question
- Saying the resolved secret is stored in the config topic (only the ${...} placeholder is)
- Thinking substitution happens at submit time rather than connector start on each worker
- Forgetting the secret file must exist on every worker node
- Getting the syntax wrong — it's ${name:path:key}, three parts