Service Account & Multi-ORG support for Enterprise Clients#36
Service Account & Multi-ORG support for Enterprise Clients#36jworkmanjc wants to merge 4 commits intomasterfrom
Conversation
| continue | ||
| for org_id in org_ids: | ||
| org_label = org_id if org_id else "(no x-org-id)" | ||
| print(f"[ORCHESTRATOR] Organization context: {org_label}") |
|
|
||
| count_body = {'service': [service], 'start_time': start_iso, 'end_time': end_iso} | ||
|
|
||
| print(f"[ORCHESTRATOR] Querying Count URL: {count_url} | Org: {org_label} | Service: {service}") |
| response = requests.post(count_url, json=count_body, headers=headers) | ||
| response.raise_for_status() | ||
| total_events = json.loads(response.text).get('count', 0) | ||
| print(f"Total events found for org {org_label} | {service}: {total_events}") |
| total_events = json.loads(response.text).get('count', 0) | ||
| print(f"Total events found for org {org_label} | {service}: {total_events}") | ||
| except Exception as e: | ||
| print(f"Failed to get event count for org {org_label} | {service}. Defaulting to full slice. Error: {e}") |
| total_events = MAX_EVENTS_PER_WORKER | ||
|
|
||
| if total_events == 0: | ||
| print(f"No events for org {org_label} | {service} between {start_iso} and {end_iso}. Skipping.") |
|
|
||
| num_chunks = max(1, math.ceil(total_events / MAX_EVENTS_PER_WORKER)) | ||
| time_slices = chunk_time_range(window_start, window_end, num_chunks) | ||
| print(f"Splitting org {org_label} | {service} into {num_chunks} chunk(s) for the workers.") |
| f"Queued job into Pub/Sub: org={org_label} | {service} | " | ||
| f"{message_body['start_time']} to {message_body['end_time']}" |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Reviewed by Cursor Bugbot for commit 421f6f7. Configure here.
| _JC_SERVICE: "CHANGEVALUE" # Can accept multiple values' for example: _JC_SERVICE: directory,systems or gather logs from all services with the _JC_SERVICE: all. Available services: | ||
| # Authentication: APIKey (JumpCloud API key in the api-key secret) or ServiceToken (OAuth client credentials in the form clientID:clientSecret in the same secret). | ||
| _JC_AUTH_TYPE: "ServiceToken" | ||
| _JC_MULTI_ORG: "false" |
There was a problem hiding this comment.
Critical substitution variables removed but still referenced
High Severity
_JC_API_KEY, _JC_ORG_ID, and _JC_SERVICE were removed from the substitutions block but are still referenced in build steps — $_JC_API_KEY in the secrets step, $_JC_ORG_ID in the secrets step, and $_JC_SERVICE in the orchestrator --set-env-vars. Without definitions, Cloud Build either fails or resolves them to empty strings, silently creating secrets with no value and deploying the orchestrator with an empty service env var that causes all services to be skipped.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 421f6f7. Configure here.
| _JC_ORG_ID: "CHANGEVALUE" | ||
| _JC_SERVICE: "CHANGEVALUE" # Can accept multiple values' for example: _JC_SERVICE: directory,systems or gather logs from all services with the _JC_SERVICE: all. Available services: | ||
| # Authentication: APIKey (JumpCloud API key in the api-key secret) or ServiceToken (OAuth client credentials in the form clientID:clientSecret in the same secret). | ||
| _JC_AUTH_TYPE: "ServiceToken" |
There was a problem hiding this comment.
Default auth type contradicts documentation and code
Medium Severity
_JC_AUTH_TYPE defaults to "ServiceToken" in cloudbuild.yaml, but the README documents APIKey as the default, and the Python code in main.py also falls back to JC_AUTH_TYPE_API_KEY. Users who don't explicitly change this value will be configured for OAuth client credentials auth, which requires a clientID:clientSecret secret format and a mandatory org ID — likely failing at runtime if they only have a standard API key.
Reviewed by Cursor Bugbot for commit 421f6f7. Configure here.


Issues
What does this solve?
Two issues, the addition of service account credentialing instead of API key. This allows customers to scope credentials to just a read only DI role and create a service token for just this tool.
Also included is the Multi-Org support for MTP admins or some Enterprise Portal customer who wishes to get data about many of their orgs at once without multiple deployments.
This creates a bucket item per org, per timestamp.
Is there anything particularly tricky?
Review of the implementation of MultiOrg support.
How should this be tested?
Test with a service account token, test with MTP & multiple organizations:

When multiple orgs have data in a given timeslot: their orgID will be in the bucket name item
The orchestrator will process each org events separate

Screenshots
Note
High Risk
High risk because it changes JumpCloud authentication behavior (API key vs OAuth bearer token) and alters orchestration/worker message and output naming semantics for multi-tenant processing, which can break data collection if misconfigured.
Overview
Adds OAuth client-credentials (ServiceToken) support alongside API key auth for Directory Insights; Orchestrator/Worker now build request headers via
get_jc_request_headers()and fetch bearer tokens at runtime whenjc_auth_type=ServiceToken.Introduces multi-organization mode (
jc_multi_org) where the Orchestrator loops over a comma-separated org list, includesorg_idin each Pub/Sub job, and the Worker requires/uses thatorg_idand prefixes GCS object names to avoid cross-org overwrites.Updates
cloudbuild.yamlto validate new substitutions, passjc_auth_type/jc_multi_orgenv vars, and to create-or-version Secret Manager entries on each build; README is expanded with deployment/substitution and behavior documentation.Reviewed by Cursor Bugbot for commit 421f6f7. Bugbot is set up for automated code reviews on this repo. Configure here.