Skip to content

Platform Connectors

The compliance scan reads more than your cloud accounts. The Stratus mods also grade your identity provider (Okta) and your source-control platform (GitLab or GitHub) against the FedRAMP 20x Key Security Indicators that depend on them. Those indicators cover who can authenticate into the boundary, with what factors, and how code and secrets reach production. These platforms sit outside your cloud accounts, so the scanner cannot reach them through an IAM role. Instead you create a read-only identity on each platform and hand its credential to the scanner.

Quick Summary

  • Each connector wants a dedicated, read-only, platform-owned identity: an Okta API token on a service account holding Read-only Administrator, a GitLab group access token with the read_api scope, and a GitHub App with read-only permissions. Nothing in the benchmarks writes to any of these platforms.
  • Credentials go into AWS Secrets Manager secrets in the scanner account, one per platform, as small JSON objects with fixed key names. The scanner stack takes each secret's ARN as an optional parameter and reads nothing else from Secrets Manager.
  • Every connector is optional. Leave a platform's parameters blank if you do not use it; its checks then report an error status in the snapshot and are ignored downstream.
  • GitLab and GitHub checks also need a scope: which GitLab group IDs and which GitHub organizations sit inside your authorization boundary. Those are stack parameters too. Okta needs no scope; one credential sees one org.
  • Every connector has a preflight check that alarms when the credential cannot see what it should. Read it first: an unreachable scope returns no rows, which looks like a clean result.

Navigation

The credentials are created on each platform's own admin console and stored in your scanner account, not in GRC-ITSM. To confirm the results landed, open Compliance > KSIs in the GRC-ITSM navigation and look for the check IDs of the platforms you configured.


How the connectors fit

The scanner task starts, reads each Secrets Manager secret the stack points it at, and exposes the values to the matching Steampipe plugin as environment variables. The plugin authenticates with them for the duration of the run. The Stratus mods then grade what the credential can see, bounded by the scope you set.

Platform Identity you create Values the scanner needs Where they go
Okta API token on a dedicated service account with Read-only Administrator Org URL, API token Secrets Manager secret, keys OKTA_CLIENT_ORGURL and OKTA_CLIENT_TOKEN; its ARN is the stack's OktaSecretArn parameter
GitLab Group access token, Owner role, read_api scope API base URL, token Secrets Manager secret, keys GITLAB_ADDR and GITLAB_TOKEN; its ARN is the stack's GitlabSecretArn parameter
GitHub GitHub App installed on the organization App ID, installation ID, private key Secrets Manager secret, keys GITHUB_APP_ID, GITHUB_APP_INSTALLATION_ID, and GITHUB_APP_PRIVATE_KEY; its ARN is the stack's GithubSecretArn parameter

Stack parameters set the scope; two are required for their platform. Each maps to a Powerpipe mod variable of the same meaning, and comma-separated values become a list:

Stack parameter Platform What it does
GitlabGroupIds GitLab Numeric group IDs in the assessment boundary. Required: while it is blank, no GitLab check returns a row
GitlabProjectIds GitLab Optional numeric project IDs that narrow the project-scoped checks when a group pulls in projects outside the boundary
GitlabTier GitLab Your license tier (free, premium, ultimate; the default unknown means not yet asserted). Premium-gated columns read the same on Free as they do when a feature is off, so those checks report informational until you assert the tier
GithubOrganizations GitHub Organization logins in the assessment boundary. Required for every GitHub check
GithubRepositories GitHub Optional exact owner/name scope for organizations too large for the GitHub search spine (over 1,000 repositories)
GithubBaseUrl GitHub Blank for github.com. The instance root URL for GitHub Enterprise Server; see Public GitHub versus GitHub Enterprise Server

Have the group IDs and organization logins ready when you deploy. Changing them later is a stack update, and the next scheduled run grades the new boundary.


Okta

The Okta connector grades Okta as the identity provider for your service: authentication policies and their rules, global session policies, password policies, MFA enrollment policies, authenticators, network zones, administrator role assignments, and account activity. The connector covers 4 KSIs and 23 checks. Every check grades Partial, because Okta governs authentication only for what is federated through it, and configuration cannot show which ordered policy rule a given sign-in matched.

Which identity, and why

The Okta plugin accepts an API token or an OAuth 2.0 service app. Use the API token on a dedicated service account.

Approach Verdict
API token on a dedicated service account Use this. The token inherits its creating account's admin role, so least privilege is set by the role you give that account. It reads every table the benchmark uses, can be bound to a network zone, is attributable to its own account in the System Log, and expires after 30 days without use, so an abandoned token dies on its own
OAuth 2.0 service app Cleaner in principle, but the plugin requests a fixed scope list in this mode that omits the authenticators and network-zones scopes. Three checks then return 403 regardless of what you grant the app. Usable only if you accept losing those checks
API token on a person's Super Administrator account Do not. It carries that person's full authority, dies when they leave or change roles, and is itself a finding under the DISA Okta STIG

What to create

  1. A dedicated user. In the Okta Admin Console, go to Directory > People > Add person. Give it a name that says what it is (for example svc-compliance-scanner), a monitored mailbox, and a password and authenticator that meet your own policies; the account is graded against them like everyone else. Do not add it to any group that grants application access.
  2. The Read-only Administrator role, unconstrained. In Security > Administrators > Add administrator, select the user, role Read-only Administrator, resource constraint all. This role reads users, groups, applications, policies and rules, authenticators, and network zones. It can change nothing. Leave it unconstrained: a role limited to a group set returns a partial user and group list with no error, and the benchmark grades that slice as if it were the whole org.
  3. The token, created as that user. An API token belongs to whoever creates it, so sign in as the service account, then Security > API > Tokens > Create token. Name it after the consumer (compliance-scanner). Copy the value once; Okta never shows it again. Okta's API token guide covers the token page, including the network-zone control in the next step.
  4. Bind it to a network zone. Where the token page offers Token can be used from, set it to an IP zone containing only the scanner's egress addresses (the NAT gateway IPs of the scanner's subnets, or the task's public IP range). Create the zone first under Security > Networks if needed. If the control is absent, the feature is not enabled on your org; record that as a deviation rather than skipping the step silently.
  5. Store it in Secrets Manager, in the scanner account, as a JSON object with exactly these keys:

    {
      "OKTA_CLIENT_ORGURL": "https://your-org.okta.com",
      "OKTA_CLIENT_TOKEN": "00..."
    }
    

    The org URL includes the scheme and is the Okta cell hostname (your-org.okta.com, your-org.okta-emea.com, or a custom domain the API answers on), not your company's own domain. Pass the secret's full ARN, including the six-character suffix Secrets Manager appends, as the OktaSecretArn stack parameter.

What the role unlocks

The benchmark is split into sub-benchmarks by what the credential and org can satisfy, so a constrained run degrades cleanly instead of erroring:

Sub-benchmark Checks Requires
Preflight 1 (outside the 23) Nothing. Confirms the org is on Identity Engine and the credential reads the policy set, and reports the active user count for you to compare against Directory > People
Portable 15 Read-only Administrator with either credential type
API token 3 An API token (authenticators and network zones)
Org admin 3 The service account holds Organization Administrator. In practice Okta serves a user's administrator role assignments only to Super and Organization Administrators; a Read-only Administrator gets a permission error, so these three checks error under that role
Feature gated 2 The Group Owners feature and the MFA enrollment policy API. Without them the tables answer 401 or 400 regardless of role

Read-only Administrator covers 20 of the 23 checks. For the remaining three you have two options. Raise the service account to Organization Administrator, accept that the token can then write users, groups, and apps, and record that as a deviation. Or take administrator role assignments as manual evidence from Security > Administrators. Never grant Super Administrator.

Exemptions and the service account

Four checks read a per-user exemption carried in the user profile. An administrator first adds three custom string attributes to the default Okta user profile under Directory > Profile Editor: compliance_exempt, compliance_exemption_expiry, and compliance_exemption_reason. Until they exist, every user reads as not exempt. The format matches the cloud resource tags: a colon-separated control list, an expiry as YYYY-MM-DD, and a reason.

The service account itself is the first candidate for an exemption. It holds an administrator role and never signs in interactively, so after the inactive-account threshold it alarms as an inactive administrator by design. Set its exemption for the inactive-administrator checks with a reason and an expiry rather than special-casing it.

Keeping the token alive

The token expires after 30 days without use. The scheduled scan keeps it alive; a paused schedule longer than that kills the token, and the next run reports an authentication failure rather than a finding. Re-create the token; do not widen anything. Revoke it under Security > API > Tokens when the connector is retired.


GitLab

The GitLab connector grades the deploy path: protected branches and merge approval settings, together with pipeline activity. It also grades CI variables at group and project level, webhooks, two-factor enforcement, group-mediated membership, and push rules. Coverage is 7 KSIs and 15 checks. That coverage is narrow by design: a repository platform normally holds no federal customer data, so it enters the assessment only where code, credentials, or dependencies from it reach production. Every check grades Partial because configuration cannot prove three things the KSIs ask for: that the mechanism is persistently reviewed, that the whole offering (not just the source-control component) satisfies the indicator, and what a CI job did at runtime rather than that it ran. Both GitLab.com and self-managed instances are supported.

Which identity, and why

Approach Verdict
Group access token Use this. Group-owned rather than person-owned, consumes no seat, and you pick the role at creation, so it is a member of the group the moment it exists. Revocable independently of any person, and attributable to its own bot identity in audit events
Group service account Works only with a separate step people forget: a service account is created for a group but is not automatically a member of it. Issued without a membership grant it authenticates and sees nothing: the group returns 404
Project access token Too narrow. Scoped to one project, so it can never read group variables, group webhooks, or the member roster

What to create

A group access token on the top-level group in the assessment boundary, with the Owner role and the read_api scope, and nothing else.

Group access tokens are available on any tier of a self-managed or Dedicated instance. On GitLab.com they require a Premium or Ultimate subscription and are not available on a trial. A GitLab.com group on the Free tier has no group-owned identity to offer; the fallback there is a personal access token on a dedicated machine user with the same read_api scope, accepting that it consumes a seat and is person-shaped. That machine user must hold Owner on every group in scope, because the Owner-gated tables error rather than return empty when the role is lower.

  1. In that group, go to Settings > Access tokens (GitLab's group access token guide walks through the page).
  2. Role: Owner. Owner here is a visibility level, not a write permission; the scope gates writes. Owner plus read_api can read group variables, webhooks, and the member roster that GitLab hides from Maintainer, and cannot change a setting, merge anything, or touch a repository.
  3. Scopes: read_api only. Do not add self_rotate; it is the one scope that permits a write.
  4. Expiry: set one and put the renewal date on a calendar. GitLab defaults the expiry to 365 days from creation and caps the lifetime; on newer self-managed releases an administrator can change that cap.
  5. Store it in Secrets Manager, in the scanner account, as a JSON object with exactly these keys:

    {
      "GITLAB_ADDR": "https://gitlab.example.com/api/v4",
      "GITLAB_TOKEN": "glpat-..."
    }
    

    GITLAB_ADDR is the REST API base URL, including the /api/v4 suffix. For GitLab.com it is https://gitlab.com/api/v4. Pass the secret's full ARN as the GitlabSecretArn stack parameter.

Which group

A token scoped to a group that holds few projects yields a small assessment however high its role. Confirm with whoever owns the instance which top-level group actually holds the repositories in scope, and grant against that.

  • Group IDs, not paths, go into gitlab_groups. The numeric ID shows on the group's overview page. A project ID mistaken for a group ID fails as 404 Group Not Found, which looks identical to a permissions problem.
  • List every group in the chain, subgroups included. Project-scoped checks reach subgroup projects from the top-level group alone, but the group-scoped checks (settings, CI variables, push rules, webhooks, roster) read each group's own objects. An unlisted subgroup loses that coverage. One check builds its roster from the configured IDs only, so omitting a subgroup can manufacture findings.
  • Use gitlab_projects to pin the project-scoped checks to a narrower boundary when a parent group also holds siblings you do not want graded.

What each grant unlocks

Grant Unlocks Checks
Any role on the group Groups, group projects, project members, pipelines, push rules 6 portable checks and the preflight
Project Maintainer (inherited from group Owner) Protected branches, project CI variables 2
Group Owner Group CI variables, group webhooks, group members, group identity-provider bindings 6
Instance administrator Instance settings 1

Group Owner covers 14 of the 15 checks. The last one reads instance-wide settings and needs an instance administrator token, which is a large grant for a single finding on a production instance. The benchmark handles its absence: that check reports nothing rather than erroring. Grant it only if someone independently wants it scored.

Exemptions

GitLab has no key-value carrier on any gradeable object, so one project topic encodes every field: compliance-exempt:ksi-cmt-vtd:2026-12-31 (prefix, control code, expiry). Matching is case-insensitive, an expired date reports alarm, and there is no carrier for a reason. Only the six checks whose graded resource is a project can be exempted; groups, members, variables, webhooks, protected branches, push rules, and settings cannot.

Not assessed on GitLab

Two KSIs the GitHub connector covers have no GitLab check. Both are tooling gaps rather than platform gaps. Change logging is one: the plugin exposes no audit-event table; GitLab has audit events at Premium and above. Supply-chain monitoring is the other: no dependency, SBOM, or scanner table; GitLab has dependency scanning at Ultimate. The approvals-required-before-merge check is also not assessed, because the only approvals column the plugin exposes is deprecated by GitLab with undefined behavior under approval rules.


GitHub

The GitHub connector grades the same deploy path: branch protection on the default branch, code owner review, workflow run history, Actions secret rotation age, webhooks, dependency alerts and SBOM availability, two-factor enforcement, SAML, team-mediated access, and audit log coverage. That is 9 KSIs and 24 checks, every one graded Partial for the same three reasons as GitLab.

Which identity, and why

GitHub has no service-account object. Of the three ways to authenticate a recurring assessment, one is right:

Approach Verdict
GitHub App Use this. Organization-owned, consumes no seat, permissions declared per resource and approved at install, private key rotatable independently of any person, installation scoped to selected repositories if you choose, and API calls attributable to the App in the audit log
Machine user with a personal access token Works, but consumes a paid seat, is a person-shaped account with a password and 2FA to manage, and its token carries the user's whole access rather than a declared permission set
A person's personal access token Fine for a one-off. Not appropriate for a recurring control assessment: it inherits everything the person can see and dies when they change roles

A GitHub App authenticates by signing a JWT with its private key and exchanging it for a short-lived installation token. The Steampipe plugin does this for you; you supply the App ID, the installation ID, and the private key.

Permissions

Read-only throughout. Each row maps to the tables the 24 checks read.

Repository permissions

Permission Access Needed for
Metadata Read Every table. The baseline
Administration Read Branch protection. Ten of the 24 checks depend on this
Contents Read CODEOWNERS, SBOM, collaborators
Actions Read Workflow run history
Secrets Read Actions secret metadata for rotation age. Metadata only; the API never exposes secret values
Webhooks Read Repository webhooks
Custom properties Read The exemption carrier. Without it every exemption silently stops applying: no error, the checks alarm on repositories you exempted

Organization permissions

Permission Access Needed for
Members Read Organization, teams, external identities
Variables Read Organization Actions variables
Custom properties Read Organization-level definition of the exemption properties
Webhooks Read Organization webhooks
Administration Read Default repository permission, two-factor requirement, SAML provider; also the path to the audit log on Enterprise

Dropping repository Administration costs ten checks, and they fail in the worst way: as "no branch protection rule matching default branch", indistinguishable from a genuine finding. If you cannot grant it, exclude those checks rather than read the output as real.

What to create

  1. Create the App. Go to Organization settings > Developer settings > GitHub Apps > New GitHub App (GitHub's guide). Homepage URL can be anything. Uncheck Webhook > Active; the App receives nothing. Set the permissions above. Under "Where can this app be installed?", choose Only on this account.
  2. Note the App ID from the App's settings page.
  3. Generate a private key on the same page under Private keys. A .pem file downloads. Treat it as a credential: keep it out of any repository and restrict its file permissions.
  4. Install the App on the organization. Choose All repositories, or Only select repositories to scope the assessment boundary at the App rather than through the github_repositories variable.
  5. Get the installation ID. After installing, the browser URL ends in it: https://github.com/organizations/<org>/settings/installations/<INSTALLATION_ID>.
  6. Store it in Secrets Manager, in the scanner account, as a JSON object with exactly these keys. Paste the full PEM text as the private-key value; the Secrets Manager console keeps its line breaks, and the scanner also accepts a value stored with literal \n sequences:

    {
      "GITHUB_APP_ID": "123456",
      "GITHUB_APP_INSTALLATION_ID": "78901234",
      "GITHUB_APP_PRIVATE_KEY": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
    }
    

    Pass the secret's full ARN as the GithubSecretArn stack parameter. At startup the scanner writes the key to a file for the Steampipe GitHub plugin, which reads it, signs a JWT with it, and exchanges that for a short-lived installation token. You can then delete the downloaded .pem; the secret is the copy of record.

Public GitHub versus GitHub Enterprise Server

Leave GithubBaseUrl blank for public GitHub. The plugin then calls the public API at https://api.github.com, which covers every organization hosted on github.com, including GitHub Enterprise Cloud.

For a self-hosted GitHub Enterprise Server instance, set GithubBaseUrl to the instance root URL (scheme and host, no path), for example https://github.example.com. The plugin appends the REST (/api/v3) and GraphQL (/api/graphql) paths itself.

More than one organization

An App installation sees one organization, and the scanner stack carries one installation, so GithubOrganizations holds one value today: the organization the App is installed on. The parameter accepts a list because the underlying checks do; the single-installation limit is in the stack, not the checks. Covering several organizations from one scanner is not supported today; deploy a second scanner stack for a second organization.

Plan gating and exemptions

Plan gates two things, and neither is a control failure. The audit log requires GitHub Enterprise Cloud, so the two change-logging checks are excluded below that plan. SAML single sign-on is likewise an Enterprise capability, so that check reports not-assessed rather than alarming on a Free or Team plan.

Exemptions ride on repository custom properties named compliance_exempt, compliance_exemption_expiry, and compliance_exemption_reason, defined at the organization level, with the same format and expiry-means-alarm semantics as the cloud resource tags. Custom properties are organization-governed, so a repository maintainer cannot self-grant an exemption. Organizations, members, and branch protection rules cannot carry one.


Verify the setup

Do these before reading any benchmark result, in this order, because each connector's failure mode is silence rather than an error.

  1. Okta. The preflight reports ok, not alarm. An alarm means the org is on Classic Engine or the credential cannot read policies. Then compare the active user count in the preflight reason against Directory > People filtered to Active; a smaller number means the role is constrained and every user-graded result is partial. If a fresh token is rejected, confirm the org URL first: https://<host>/.well-known/openid-configuration returns an issuer for a real org and a bare 403 for a hostname that is not one.
  2. GitLab. With the token, GET /user returns 200 and GET /groups returns a non-empty array that includes the target group. A 401 against a self-managed instance usually means GITLAB_ADDR points at the wrong host; a valid token sent to the wrong host looks exactly like a dead one. A 404 on the group means the identity holds no role on it. Then the preflight must report ok: an unreachable scope makes 14 of 15 checks return zero rows, which reads as no findings.
  3. GitHub. The preflight reports ok. A misspelled organization login, a lapsed grant, or an organization above the search cap all produce zero rows and no errors, and the preflight alarms on each.
  4. In GRC-ITSM, open Compliance > KSIs after the next ingestion and confirm the check IDs for each platform you configured carry fresh validation records.

Keeping the credentials honest

  • Nothing here writes. If any connector ever needs a write permission or scope, that is a bug, not a requirement.
  • Rotate on a schedule and revoke at the end. GitHub App keys rotate without interrupting the installation. GitLab tokens have a mandatory expiry; revoke rather than let them lapse. Okta tokens die after 30 idle days on their own.
  • Watch for offboarding. Every identity above is platform-owned, which is the point: a person's departure never silently breaks the scan.
  • Keep the values out of transcripts and tickets. Okta tokens are 42 characters starting with 00; GitLab tokens start with glpat- and newer ones carry dotted segments after the secret. Mask both before pasting logs anywhere.

These are external connections

Each connector is a read path from a third-party platform into your scanner account, and the results flow on into the platform. Record each one in the System Security Plan alongside the Steampipe/Powerpipe Stack interconnection: the identity, its role and scopes, the secret that holds it, and the rotation schedule. Every check on these platforms grades Partial by design, so the output is evidence for the KSIs it covers and input to your third-party documentation for the platform, not a certification of Okta, GitLab, or GitHub.

Related documentation