A beautiful onboarding automation exists:Documentation Index
Fetch the complete documentation index at: https://docs.provisionr.io/llms.txt
Use this file to discover all available pages before exploring further.
- HRIS webhook triggers when someone joins
- Script creates Okta account
- Script adds user to appropriate Okta groups
- Okta provisions access to integrated apps
- Google Workspace uses different group naming conventions than Okta
- AWS doesn’t integrate with Okta groups directly (needs SSO + permission sets)
- GitLab has its own permission model (Guest/Reporter/Developer/Maintainer)
- Slack channels can’t be managed via Okta
- On-prem systems don’t talk to Okta at all
- 8 different scripts, each handling one system
- 3 different authentication methods
- 5 different permission models
- No consistent way to track what happened
- Manual fallback for systems that can’t be automated
The Problem With Multiple Systems
Every system thinks about access differently: Okta: Groups. Users are in groups. Groups are assigned to apps. Google Workspace: Groups + organizational units. Groups for email distribution. OUs for organizational hierarchy. Sometimes groups control app access. Sometimes OUs do. AWS: IAM roles + permission sets + SSO integration. Users don’t directly have permissions. They assume roles. Roles have permission sets. SSO maps Okta groups to permission sets. GitLab: Project-based permissions. Users can be Guest/Reporter/Developer/Maintainer at the project or group level. Permissions cascade down hierarchy. Slack: Channel-based access. Users are invited to channels. Channels can be public, private, or shared. Some channels are managed via SSO groups. Some are manual. Jira: Project roles. Users have roles within projects. Roles grant permissions. Roles can be granted to groups or individuals. Salesforce: Permission sets + profiles. Profiles are legacy (assigned to users). Permission sets are modern (assigned to users, additive). Permission set groups combine multiple permission sets. GitHub: Organization-based. Users are in organizations. Organizations have teams. Teams have permissions on repositories. Permissions are None/Read/Triage/Write/Maintain/Admin. Eight different permission models. Mapping between them. Keeping them in sync.Why This Is Hard
No common permission model exists
“Read access” means different things in different systems. GitLab: Reporter role (can clone, can’t push). GitHub: Read permission (can clone, can’t push, can open issues). AWS S3:
s3:GetObject permission (can download files). Google Drive: “Viewer” role (can view, can’t edit or share). Jira: “Browse” permission (can view issues in project). When someone is a “Senior Engineer” and should have “read access to production systems,” that translates to 5 different things in 5 different systems.Different group structures create mapping complexity
Okta groups look like:
engineering-team. Google Workspace groups look like: engineering@company.com. AWS permission sets are named: EngineeringTeamReadOnly. GitLab groups are nested: engineering/platform and engineering/frontend. Keeping these in sync is non-trivial. If engineering-team updates in Okta, which Google group, which AWS permission set, and which GitLab groups need to update?Nested and cascading permissions differ by system
Some systems have flat permissions. Some have hierarchical. In GitLab, Developer access at the
engineering group level means automatic Developer access in all subgroups (engineering/platform, engineering/frontend, etc.). In Okta, membership in engineering-team doesn’t mean automatic membership in engineering-platform-team. Same conceptual hierarchy. Different technical models.Different API patterns require different handling
Okta API: RESTful, well-documented, rate-limited to 10,000 requests/minute. Google Workspace API: RESTful, multiple quota limits (per user, per project), eventual consistency. AWS API: Dozens of services, each with own API, permissions spread across IAM/SSO/Organizations. GitLab API: RESTful, rate-limited to 300 requests/minute per user. Slack API: RESTful, rate-limited (Tier 2, Tier 3, Tier 4 methods), requires separate OAuth scopes per action. Each API has quirks. Each has different error handling. Each has different rate limits.
Order dependencies exist
Provisioning can’t happen in arbitrary order. The Okta account must exist before adding the user to Okta groups. Okta groups must be assigned to AWS SSO before AWS permission sets apply. The GitLab account must exist before adding the user to GitLab groups. Google groups must exist before adding users to them. If the user is created in Google first, then Okta, Google provisioning might fail because the Okta account doesn’t exist yet and Google is configured to sync from Okta. The dependency graph must be modeled and provisioning must happen in the right order.
Partial failures create messy state
Provisioning access to 8 systems. Six succeed. Two fail (API timeout, rate limit, service outage). Now what? Roll back the six that succeeded? (Not always possible. Some systems don’t support rollback.) Retry the two that failed? (Idempotency isn’t guaranteed. Retrying might create duplicates.) Manual cleanup? (Defeats the purpose of automation.) Leave it broken and hope someone notices? (This is what usually happens.)
Drift happens constantly
Access is provisioned via automation. Someone manually adds access directly in a system (Slack admin invites someone to a channel). The automation doesn’t know about manual changes. Result: Drift. The source of truth (the automation) disagrees with reality (what’s actually configured). How is this detected? How is it reconciled?
The Naive Approach (And Why It Fails)
Approach 1: Big bash scriptWhat Actually Works: An Orchestration Layer
Companies that have solved this build an orchestration layer—a purpose-built system that:- Models access policies (who should have what)
- Translates policies to system-specific configurations (Okta groups, AWS permission sets, GitLab roles)
- Provisions in the correct order (handles dependencies)
- Tracks execution state (what succeeded, what failed, what’s pending)
- Retries failures intelligently (exponential backoff, partial retry)
- Detects drift (compares policy to reality)
- Provides audit logs (every change is logged with justification)
Layer 1: Policy Definition
Layer 2: System Adapters
Each system gets an adapter that knows how to interact with that system’s API:Layer 3: Orchestration Engine
The orchestrator:- Reads the policy (who should have what)
- Determines current state (what they actually have)
- Calculates delta (what needs to change)
- Orders operations (handles dependencies)
- Executes actions (calls system adapters)
- Tracks state (logs success/failure)
- Retries failures (intelligently)
- Reports results (what happened, what’s pending)
Layer 4: Drift Detection
Continuously compare policy (expected state) to reality (actual state):Building vs. Buying
Build a custom orchestration layer: Pros: Full customization. No vendor lock-in. Optimized for exact requirements. Cons: 6-12 months of engineering effort. Ongoing maintenance (APIs change, new systems added). Need to handle edge cases (rate limits, partial failures, retries, drift). Need to build UI for policy management. Need to build audit logging. Use a purpose-built platform: Pros: Immediate functionality. Pre-built adapters for common systems. Battle-tested error handling. Audit logging out of the box. Cons: Monthly/annual cost. Vendor dependency. May not support exact workflows. Customization limited to what vendor allows. The hybrid approach: Use a platform for common systems (Okta, Google, AWS, Slack, GitLab). Build custom adapters for niche or internal systems. This delivers 80% of functionality immediately and allows extension for unique needs.Real-World Example: Onboarding a Sales Engineer
Walk through provisioning Sarah, a new Sales Engineer: 1. Policy says Sarah needs:- Okta:
sales-team,engineering-team - Google:
sales@company.com,engineering@company.com - Salesforce: Sales Engineer profile
- GitLab:
customer-solutionsgroup (Reporter role) - Slack:
#sales-team,#engineering-general - Jira: Sales projects (Reporter role)
The Bottom Line
Cross-system orchestration is hard because every system has a different permission model, APIs have different quirks and rate limits, operations have dependencies, partial failures require sophisticated retry logic, and drift happens when people make manual changes. The solution isn’t better integration APIs (those help, but don’t solve the problem). The solution is an orchestration layer that models policies abstractly, translates to system-specific configurations, handles dependencies, retries, and drift, and provides auditability. Organizations building this should budget 6-12 months. Organizations buying should ensure the platform supports their systems and allows customization for edge cases. Either way, don’t underestimate this problem. It’s harder than it looks.Next up: The Policy-First Approach—building access management from first principles instead of retrofitting policy onto existing systems.
Want to see cross-system orchestration in action? Check out Provisionr’s integration architecture →