Schedule icon
Request icon
Script icon
If icon
Create icon
SlackIncomingWebhook icon
Log icon

Okta SAML SSO App Certificate and Configuration Health Probe

Probe Okta SAML apps for expiring signing certificates, zero assignees, recent disables, and sign-on mode anomalies, then open a GitHub issue and warn Slack.

Categories
Infrastructure

Okta SAML apps break quietly: a signing certificate lapses over a long weekend, a team reorg leaves an app with nobody assigned, or a sign-on mode gets flipped during a migration and nobody notices until partner logins fail. This probe runs on a schedule, asks the Okta Applications API for the whole inventory, and reduces the response to a verdict, a certificate expiry list, and a list of dead or misconfigured apps. Anything it finds becomes a labeled GitHub issue plus a Slack warning, while a clean day simply records itself as a green execution with outputs that prove the check ran.

How it works

  1. The daily_probe trigger (io.kestra.plugin.core.trigger.Schedule, cron 0 6 * * *, shipped with disabled: true) starts the probe every day at 06:00 UTC once you enable it.
  2. list_saml_apps (io.kestra.plugin.core.http.Request) calls GET /api/v1/apps?limit=200 on {{ inputs.okta_org_url }} with an SSWS header built from the OKTA_API_TOKEN secret, returning status, sign-on mode, signing metadata, and settings for every app in the org.
  3. assess_saml_apps (io.kestra.plugin.scripts.python.Script) keeps the apps that are SAML, either because signOnMode says so or because settings.signOn still carries an ACS URL or an audience, and scores each one against four rules: certificate notAfter or expiresAt dates inside days_warn_threshold, zero known assignees, apps disabled within the last 30 days, and sign-on anomalies such as a deprecated SAML_1_1 app, a bookmark app that still exposes SAML endpoints, or a SAML_2_0 app with no audience or ACS URL. Results are published through the ::json:: output protocol as verdict, expired_soon, dead_apps, and checked_at.
  4. issue_gate (io.kestra.plugin.core.flow.If) branches on issue_count. When it is over zero, create_issue (io.kestra.plugin.github.issues.Create) opens an issue in github_repository with both lists rendered as markdown and alert_slack (io.kestra.plugin.slack.notifications.SlackIncomingWebhook) posts the verdict and summary; otherwise log_all_clear records the all-clear.
  5. The errors handler posts to Slack if any step fails, because a probe that crashed looks identical to a probe that found nothing.

What you get

  • A verdict of clean or action_required on every run, with the counts behind it exposed as flow outputs.
  • An expired_soon list carrying the app label, the certificate date, the days remaining, and an admin console link, ordered most urgent first.
  • A dead_apps list where every entry states its reasons, which are zero_assignees, recently_disabled, deprecated_signon_mode, signon_mode_mismatch, missing_signon_mode, unknown_signon_mode, or missing_saml_endpoints.
  • A labeled GitHub issue with both lists whenever the probe finds anything, so the work lands in the same backlog as everything else the team tracks.
  • A Slack warning carrying the verdict and a one line summary, plus a separate Slack alert if the probe itself fails.
  • A dated execution history that doubles as evidence the SAML estate was actually checked.

Who it's for

  • Identity and access engineers who own SSO application configuration in Okta and want certificate lapses surfaced before users notice them.
  • Security teams that need proof the SAML estate was checked every day rather than only when a partner reports a login failure.
  • Platform teams that already keep infrastructure in Git and prefer a reviewed YAML file to a spreadsheet of app owners and renewal dates.
  • Small IT groups without a dedicated IdP administrator, where certificate rotation currently depends on one person remembering.

Why orchestrate this with Kestra

The probe is an API call and a small script, but the value sits in the loop around them: a schedule that cannot be forgotten, a branch that separates escalation from the all-clear, an issue, an alert, and a history that proves the check ran. A cron job on a laptop can fetch the same JSON, yet it cannot open a tracked issue, notify a channel, retry a transient API failure, or show an auditor when the last probe ran. Keeping the rules, the thresholds, and the escalation in one versioned file means a threshold change becomes a reviewed pull request instead of an undocumented edit. This flow also stays deliberately separate from a quarterly entitlement recertification campaign: that one reviews which people hold which directory groups, while this one watches SSO application configuration and certificate health every day.

Prerequisites

  • An Okta org whose API token can read applications, which is the okta.apps.read scope when you use a scoped OAuth token or a classic API token with read access to apps.
  • Certificate and assignment fields vary by app type, so the probe evaluates a certificate date when the inventory carries notAfter or expiresAt under credentials, and an assignee count when it carries one of the common count or list fields.
  • A GitHub repository where the provided token is allowed to open issues, named through the github_repository input.
  • A Slack incoming webhook for the warning and the failure alert.
  • A Python runner available to Kestra for the assessment task.

Secrets

  • OKTA_API_TOKEN: Okta API token sent as an SSWS header when listing applications.
  • GITHUB_TOKEN: token used to open the issue, with permission to create issues in the target repository.
  • SLACK_WEBHOOK_URL: Slack incoming webhook URL used for warnings and failure alerts.

Quick start

  1. Add the three secrets above to the company.team namespace in Kestra under Settings, then Secrets.
  2. Point okta_org_url at your org and set github_repository to the owner/repo that should receive findings.
  3. Run the flow once by hand and confirm the verdict output, the two lists, and either the new issue or the all-clear log line.
  4. Enable the schedule when you are happy with the result. The block to enable is this one, shipped disabled so nothing fires during review:
triggers:
  - id: daily_probe
    type: io.kestra.plugin.core.trigger.Schedule
    cron: "0 6 * * *"
    disabled: true
  1. Leave it enabled and the daily run keeps the certificate and configuration picture current without anyone opening the Okta admin console.

How to extend

  • Raise or lower days_warn_threshold per environment, since a 30 day horizon suits production SAML apps while an internal tool can live with less warning.
  • Fetch exact signing certificate dates per app with GET /api/v1/apps/{appId}/credentials/keys and assignment counts with GET /api/v1/apps/{appId}/users, then merge them into apps.json before the assessment for full coverage on large orgs.
  • Lower RECENTLY_DISABLED_DAYS inside the assessment script to catch disabled apps sooner, or raise it to report only fresh deactivations.
  • Route findings to the app owner by mapping app labels to a team list and naming that team in the issue body.
  • Replace the GitHub issue with a Jira or Linear ticket by swapping create_issue for the matching plugin task.
  • Follow the Okta pagination cursor in a loop when the org has more than 200 applications, so the inventory stays complete.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.