Salesforce Setup for dfe-fetcher
What a Salesforce administrator configures so dfe-fetcher can read security and audit data from a Salesforce org (read-only, pull-mode).
Overview
dfe-fetcher authenticates server-to-server against
<login_url>/services/oauth2/token via one of two OAuth2 flows, selected by
which config fields are set:
- JWT bearer (recommended): an RS256-signed JWT with claims
{iss: client_id, sub: username, aud: login_url, exp: now+5m}, signed with the app's RSA private key. No secret stored in dfe-fetcher, only the private key. Chosen whenprivate_keyorprivate_key_secretis set. - Client credentials:
client_id+client_secretform post. Chosen when a secret is set and no private key is.
The token response returns an instance_url (e.g.
https://yourco.my.salesforce.com) that dfe-fetcher uses for all subsequent
REST calls (you can pin it with instance_url_override). The JWT aud follows
login_url (https://login.salesforce.com for production,
https://test.salesforce.com for sandboxes).
Services (each emits source tag salesforce.<service>):
setup_audit_trail- SOQL againstSetupAuditTrail; admin config changes.login_history- SOQL againstLoginHistory; login events.event_log_file- two-stage: SOQL listsEventLogFilerows in the window, then each row'sLogFilebody is downloaded and parsed from CSV to one record per line, with_dfe_fetcher_event_type/_dfe_fetcher_log_dateprovenance fields added. A window listing more than 200 files is read again in halves until each half fits, so no file is skipped.
The source is the shipped salesforce profile
(crates/fetcher/profiles/salesforce.yaml); the sources.salesforce block
below maps onto an instance of it at load. The SOQL units follow
nextRecordsUrl until the API says done; a 429 or 5xx is retried with
backoff (honouring Retry-After), a refused exchange or a 401 or 403 ends
the tick, a log file that keeps failing fails the unit's tick, and a tick that
fails does not advance the fetch window. An unknown service name or a
missing credential is refused at load, naming sources.salesforce.
Prerequisites
- A Salesforce org (production or sandbox) and an admin who can create a connected app / external client app and a permission set.
- For JWT bearer:
opensslto generate an RSA keypair and self-signed cert. - For
event_log_file: at least the free event types (EE/UE/Performance editions), or the Event Monitoring / Shield add-on for the full event-type set and longer retention. - The REST API version is selected with
api_version.
Required Permissions
All permissions are read-only. The OAuth scope only needs api (Access the
Salesforce API) - no full or write scope.
| Service | sObject / endpoint | Integration-user permission | Add-on |
|---|---|---|---|
setup_audit_trail | SetupAuditTrail (SOQL) | API Enabled + View Setup and Configuration | None (every org) |
login_history | LoginHistory (SOQL) | API Enabled + Manage Users (or View All Users) | None (every org) |
event_log_file | EventLogFile (SOQL + LogFile download) | API Enabled + View Event Log Files | Free event types with limited retention; the full event-type set and longer retention require Event Monitoring / Shield |
Source-Side Setup
There is no OpenTofu path for this source. Connected-app / external-client-app creation, certificate upload, and the pre-authorise / run-as policy are UI / CLI steps in Salesforce Setup; the provider does not cover digital-signature config cleanly. Follow the manual steps below.
Spring '26 change: new orgs can no longer create classic Connected Apps without requesting the capability from Salesforce Support. The successor is the External Client App (ECA), which supports the same JWT-bearer and client-credentials flows. Steps below use Connected App terminology; the ECA equivalents live under Setup -> App Manager -> New External Client App, with JWT-bearer configured under Flow Enablement and policies under the app's Policies tab. Existing connected apps keep working.
1. (JWT only) Generate an RSA keypair + self-signed certificate
# RSA private key - dfe-fetcher signs JWTs with this
openssl genrsa -out salesforce_dfe.key 2048
# Self-signed X.509 cert - upload this to the connected app
openssl req -x509 -new -nodes -key salesforce_dfe.key \
-subj "/CN=dfe-fetcher" -days 730 -out salesforce_dfe.crt
Keep salesforce_dfe.key secret (config or vault). Upload salesforce_dfe.crt
in step 2.
2. Create the Connected App (or External Client App)
Setup -> App Manager -> New Connected App -> Create a Connected App.
- Connected App Name:
dfe-fetcher; set a contact email. - Tick Enable OAuth Settings.
- Callback URL:
https://login.salesforce.com/services/oauth2/callback(unused server-to-server, but the form requires a value). - Selected OAuth Scopes: add Access the Salesforce API (api).
- JWT bearer only: tick Use digital signatures and upload
salesforce_dfe.crt. - Save (provisioning takes a few minutes).
Copy the Consumer Key (this is client_id) and, for client credentials, the
Consumer Secret (this is client_secret) from Manage Consumer Details.
3. Create / choose an integration user
Use a dedicated least-privilege user (a Salesforce Integration license is ideal). Assign a permission set / profile granting the permissions from the table above: API Enabled; View Setup and Configuration; Manage Users or View All Users; View Event Log Files.
4. Wire the chosen auth flow
JWT bearer - Setup -> App Manager -> dfe-fetcher -> Manage -> Edit Policies:
- Permitted Users: Admin approved users are pre-authorized.
- Assign the integration user's permission set / profile to the app
(Manage -> Profiles / Permission Sets). This pre-authorisation lets the JWT
subobtain a token without interactive consent.
Client credentials - Manage -> Edit Policies -> OAuth Policies:
- Tick Enable Client Credentials Flow.
- Set Run As to the integration user. Save and acknowledge the warning.
5. Smoke-test the token exchange
JWT bearer (via the sf CLI):
sf org login jwt \
--client-id <CONSUMER_KEY> \
--jwt-key-file salesforce_dfe.key \
--username integration@yourco.com \
--instance-url https://yourco.my.salesforce.com
Client credentials (raw token call):
curl -s https://login.salesforce.com/services/oauth2/token \
-d grant_type=client_credentials \
-d client_id=<CONSUMER_KEY> \
-d client_secret=<CONSUMER_SECRET>
A success response carries access_token and instance_url. invalid_grant
on JWT usually means: the user is not pre-authorised (step 4), the cert does not
match the key, or aud is wrong (must be https://login.salesforce.com, or
https://test.salesforce.com for sandboxes).
dfe-fetcher Configuration
Config File
JWT bearer (recommended):
sources:
salesforce:
enabled: true
# login_url: "https://login.salesforce.com" # sandbox: https://test.salesforce.com
# api_version: "v60.0"
client_id: "your-connected-app-consumer-key"
username: "integration@yourco.com"
private_key: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
services:
- name: setup_audit_trail
- name: login_history
- name: event_log_file
config:
interval: "Daily" # or "Hourly"
event_types: ["Login", "Logout", "ApiTotalUsage", "ReportExport"]
topic: "salesforce"
# filter: 'EventType == "Login"' # CEL, hot-reloaded
Client credentials: replace username + private_key with client_secret.
Recognised top-level fields: enabled, login_url, api_version, client_id,
username, private_key, private_key_secret, client_secret,
credential_secret, instance_url_override, interval_secs, services,
topic, filter. The event_log_file service config accepts interval
(Daily/Hourly) and event_types (allow-list; omit to pull every available
type).
Environment Variables
Pattern: DFE_FETCHER_SOURCES__SALESFORCE__<FIELD> (double underscores).
DFE_FETCHER_SOURCES__SALESFORCE__ENABLED="true"
DFE_FETCHER_SOURCES__SALESFORCE__CLIENT_ID="your-consumer-key"
# JWT bearer:
DFE_FETCHER_SOURCES__SALESFORCE__USERNAME="integration@yourco.com"
DFE_FETCHER_SOURCES__SALESFORCE__PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
# or client credentials:
DFE_FETCHER_SOURCES__SALESFORCE__CLIENT_SECRET="your-consumer-secret"
Secrets Manager
Keep the private key / consumer secret out of plain config. Both flows accept a secret-store spec, which takes precedence over the inline field:
sources:
salesforce:
enabled: true
client_id: "your-consumer-key"
username: "integration@yourco.com"
private_key_secret: "vault:kv/data/dfe/salesforce:private_key" # JWT bearer
# credential_secret: "vault:kv/data/dfe/salesforce:client_secret" # client credentials
services:
- name: setup_audit_trail
- name: login_history
- name: event_log_file
topic: "salesforce"
private_key_secretresolves to the full RSA private-key PEM.credential_secretresolves to the consumer secret.
Sandboxes: set login_url: "https://test.salesforce.com". My Domain: token
exchange still goes through login_url; the returned instance_url is your My
Domain host. For multiple orgs, run one dfe-fetcher instance per org, each with
its own app (and cert for JWT), integration user, and instance_id.
Verification
-
Health check. The health check is the token exchange; the
instance_urlit answers is what every data call addresses. It does not exercise per-sObject read permissions. -
Env-gated smoke tests. Live tests live in
crates/fetcher/tests/e2e/smoke_remote.rs, all#[ignore]'d. They read these from.env-cloud:SALESFORCE_CLIENT_ID(required) plus one ofSALESFORCE_PRIVATE_KEY/SALESFORCE_PRIVATE_KEY_SECRET(withSALESFORCE_USERNAME) orSALESFORCE_CLIENT_SECRET/SALESFORCE_CREDENTIAL_SECRET. Optional:SALESFORCE_LOGIN_URL,SALESFORCE_API_VERSION,SALESFORCE_INSTANCE_URL.cargo test -p dfe-fetcher --test e2e salesforce_ -- --ignored -
Common failures.
- Auth error / missing auth fields: supply
client_idplus a private key (JWT) orclient_secret(client credentials). invalid_grant(JWT): user not pre-authorised, cert/key mismatch, or wrongaud/login_url(sandbox vs production).- SOQL
403/INVALID_TYPE: integration user lacks the relevant read permission (e.g. View Event Log Files). event_log_fileempty: log files are generated asynchronously - up to ~24h forDaily, ~1h forHourly. Tight windows see empty results.
- Auth error / missing auth fields: supply
Cost
dfe-fetcher only reads. The SOQL surfaces (setup_audit_trail,
login_history) and a small built-in set of event_log_file event types
are available on standard orgs. The full EventLogFile event-type set and
longer retention may require a paid add-on (Event Monitoring or Salesforce
Shield), so there could be a cost depending on what you need. API calls
count against the org's API request allowance. Confirm any cost implications
against your own Salesforce agreement.
References
- OAuth 2.0 JWT bearer flow (server-to-server): https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_jwt_flow.htm&type=5
- Configure JWT bearer flow for External Client Apps: https://help.salesforce.com/s/articleView?id=xcloud.configure_oauth_jwt_flow_external_client_apps.htm&type=5
- EventLogFile object reference: https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_eventlogfile.htm
- EventLogFile supported event types: https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_eventlogfile_supportedeventtypes.htm
- Using Event Monitoring (REST): https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/using_resources_event_log_files.htm
- SOQL query REST resource: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query.htm