Skip to content

Service Account & Multi-ORG support for Enterprise Clients#36

Open
jworkmanjc wants to merge 4 commits intomasterfrom
CUT-5112_serviceTokenGCP
Open

Service Account & Multi-ORG support for Enterprise Clients#36
jworkmanjc wants to merge 4 commits intomasterfrom
CUT-5112_serviceTokenGCP

Conversation

@jworkmanjc
Copy link
Copy Markdown
Contributor

@jworkmanjc jworkmanjc commented Apr 10, 2026

Issues

  • CUT-5112 - Service Token Authorization
  • CUT-5115 - Multi-Org Support (Enterprise Organizations)

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
Screenshot 2026-04-10 at 2 27 32 PM

The orchestrator will process each org events separate
Screenshot 2026-04-10 at 2 28 26 PM

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 when jc_auth_type=ServiceToken.

Introduces multi-organization mode (jc_multi_org) where the Orchestrator loops over a comma-separated org list, includes org_id in each Pub/Sub job, and the Worker requires/uses that org_id and prefixes GCS object names to avoid cross-org overwrites.

Updates cloudbuild.yaml to validate new substitutions, pass jc_auth_type/jc_multi_org env 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.

@jworkmanjc jworkmanjc requested a review from kmaranionjc April 10, 2026 20:28
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.")
Comment on lines +364 to +365
f"Queued job into Pub/Sub: org={org_label} | {service} | "
f"{message_body['start_time']} to {message_body['end_time']}"
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

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"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 421f6f7. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants